zoukankan      html  css  js  c++  java
  • Java中的类

    类的定义

    类的定义由访问级别、类型、类名、是否抽象、是否静态、泛型标识、继承或实现关键字、父类或接口名称等组成。类的访问级别有public和无访问控制符,类型分为class、interface、enum。
    Java类主要由两部分组成:成员和方法。

    接口和抽象类

    定义类的过程就是抽象和封装的过程,而接口和抽象类则是对实体类进行更高层次的抽象,仅定义公共行为和特征。接口和抽象类的共同点是都不能被实例化,但是可以定义变量指向实例对象。
    下表为接口和抽象类的语法区别:

    语法维度 抽象类 接口
    定义关键字 abstract interface
    子类继承或实现关键字 extends implements
    方法实现 可以有 不能有,但在1.8后允许default实现
    方法访问控制符 无限制 有限制,默认是public abstract类型
    属性访问控制符 无限制 默认是public static final类型
    静态方法 可以有 1.8以后允许有
    static{}静态代码块 可以有 不能有
    本类型之间扩展 单继承 多继承
    本类型之间扩展关键字 extends extends
    • 抽象类在被继承的时候体现的是is-a的关系,接口在被实现的时候体现的是ca-do的关系。
      与接口相比,抽象类通常是对同类事务相对具体的抽象,通常包含抽象方法、实体方法、属性变量。如果一个抽象类只有一个方法,那么就等同于一个接口。is-a关系需要符合里氏替换原则。can-do关系要符合接口隔离原则,实现类要有能力去实现并执行接口中定义的行为。
    • 抽象类是模板式设计,而接口是契约式设计。抽象类包含一组相对具体的特征,性格偏内向。接口是开放的,性格偏外向。
    • 接口是顶级的类,虽然关键字是interface,但是编译之后的字节码扩展名还是.class。抽象类是二当家,接口位于顶层,而抽象类对各个接口进行了组合,而后实现部分接口行为,其中AbstractCollection是最典型的抽象类:
    public abstract class AbstractCollection<E> implements Collection<E> {
       //Collection定义的抽象方法,但本类没有实现
       //Collection接口定义的方法,size()这个方法对于链表和顺序表有不同的实现方式
        public abstract int size();
       //实现Collection接口的这个方法,因为对AbstractCollection的子类,他们潘孔的方式是一致的
       //这就是模板式设计,对于所有它的子类,实现共同的方法体,通过多态调用到子类具体的size()实现
        public boolean isEmpty() {
            //实现Collection的方法
            return size() == 0;
        }
                         ......
    }
    

    Java语言中类的集成采用单继承形式,避免继承泛滥、菱形继承、循环继承。在JVM中,一个雷如果有多个直接父类,那么方法的绑定机制会变得非常复杂。接口继承接口关键字是extends,允许多重继承,因为接口有契约式的行为约定,没有具体的实现和属性,某个实现类在实现多重继承后的接口时,只是说明“can do many things”。当纠结定义接口还是抽象类时,优先推荐定义为接口,遵循接口隔离原则,按某个维度划分成多个接口,然后再去用抽象类去implements某些接口,这样做可方便后续的扩展和重构。

    内部类

    在一个.java源文件中,只能定义一个与文件名完全一致的公开类,使用public class 关键字修饰。任何一个类都可以在内部定义另外一个类,前者为外部类,后者为内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。。比如属性字段private static String str,由访问控制符、是否静态、类型、变量名组成,而内部类private static class inner{},也是按照这样的顺序来定义的,类型可以为class、enum,甚至是interface,当然在内部类中定义接口是不推荐的。内部类可以是静态和非静态的,他可以出现在属性定义、方法体和表达式中,设置可以匿名出现,具体分为如下四类:

    • 静态内部类,如:static class StaticInnerClass{};
    • 成员内部类,如:private class InstanceInnerClass{};
    • 局部内部类,定义在方法或者表达式内部;
    • 匿名内部类,如:(new Thread(){}).start().
      如下为最精简的四种内部类定义方式:
    public class OuterClass {
    
    	//成员内部类
    	private class InstanceInnerClass{}
    	
    	//静态内部类
    	static class StaticInnerClass{}
    	
    	public static void main(String[] args) {
    		//两个匿名内部类,分别对应下图中的OuterClass$1和OuterClass$2
    		(new Thread() {}).start();
    		(new Thread() {}).start();
    		//两个方法内部类,分别对应下图中的OuterClass$1MethodClass1和OuterClass$1MethodClass2
    		class MethodClass1{};
    		class MethodClass2{};
    	}
    }
    

    在这里插入图片描述
    外部类和内部类之间使用$符号分割,匿名内部类使用数字进行编号,而方法内部类在类名之前还有一个编号来标识是哪个方法。静态内部类是最常用的内部表现形式,外不可以直接使用OuterClass.StaticInnerClass直接访问,类加载与外部类在同一阶段进行,在JDK源码中经常能见到,这样做的好处有:

    • 作用域不会扩散到包外
    • 可以通过外部类.内部类的方式直接访问
    • 内部类可以访问外部类中的所有静态属性和方法

    访问权限控制

    Java中的访问权限包含四个等级:

    • public:可以修饰外部类、属性、方法,表示公开的、无限制的,是访问级别最松的一级,被其修饰的类、属性和方法不仅可以包内访问,还可以跨类、挎包访问,甚至允许跨工程访问。
    • private:只能修饰属性、方法、内部类。表示私有的,是访问限制最严格的一级,被其修饰的属性或者方法只能在该类内部访问,子类,包内均不能访问,更不允许挎包访问。
    • protected:只能修饰属性和方法,表示受保护的、有限制的,被其修饰的属性和方法能被包内及包外子类访问。注意,及时并非继承关系,protected属性和方法在同一包内也是可见的。
    • 无:及无任何访问权限修饰符。可以修饰外部类、方法。不是default,因为其并不是访问权限控制符的关键字。无访问权限控制符仅对包内可见。
    访问权限控制符 任何地方 包外子类 包 内 类 内
    public OK OK OK OK
    protected NO OK OK OK
    NO NO OK OK
    private NO NO NO OK

    在定义类的时候,推荐访问控制级别从严处理:

    1. 如果不允许外部直接通过new穿件对象,构造方法必须是private。
    2. 工具类不允许有public或者default构造方法。
    3. 类非static成员变量并且与子类共享,必须是protected。
    4. 类非static成员变量且仅在本类使用,必须是private。
    5. 类static成员变量如果仅在本类使用,必须是private。
    6. 若是static成员变量,必须考虑是否为final。
    7. 类成员方法只供类内部使用,必须是private。
    8. 类成员方法只对继承类公开,那么限制为protect。

    this与super

    对象实例化时至少有一条从本类通往Object的通道,而打通这条路的两个主要工具就是this和super。this和super在很多情况下可以省略,比如:

    • 本类方法调用本类属性
    • 本类方法调用另一个本类方法
    • 子类构造方法隐含调用super()

    任何类在创建之初,都有一个默认的空构造方法,它是super()的一条默认通道。构造方法的参数列表决定了调用通路的选择;如果子类指定调用父类的某个构造方法,super就会不断向上溯源;如果没有指定,则默认调用super()。如果傅雷没有提供默认的构造方法,子类在继承时就会编译报错,如下图:
    在这里插入图片描述

    如果父类坚持不提供默认的无参构造方法,必须在本类的无参构造方法中使用super方式调用父类的有参构造方法,如:public Son(int a) {super(a);}},也是上图中quick fix的方式。

    一个实例变量可以通过this.赋值另一个实例变量,一个实例方法可以通过this.调用另一个实例方法,甚至一个构造方法都可以通过this.调用另一个构造方法。如果this和super指代构造函数,则必须位与方法体的第一行。换句话说,在一个构造方法中,this和super只能出现一个,且只能出现一次,否则在实例化对象时,会因为子类调用到多个父类构造方法而造成混乱。

    由于this和super都在实例化阶段调用,所以不能再静态方法和静态代码块中使用这两个关键字。this还可以指代当前对象,比如在同步代码块 synchronized(this){…}中,super并不具备此能力。但super也有自己的特异功能,在子类覆写父类方法时,可以使用super调用父类同名的实例方法。

    下图为this和super的异同点:
    在这里插入图片描述

    类关系

    类之间的关系有如下六种:

    1. 【继承】extends (is-a)。
    2. 【实现】implements (can-do)。
    3. 【组合】类是成员变量 (contains-a)。
    4. 【聚合】类是成员变量(has-a)。
    5. 【依赖】是除组合与聚合外的单向弱关系。比如使用另一个类的属性、方法,或以其作为方法的参数输入,或以其作为方法的返回值输出(depends-a)。
    6. 【关联】是相互平等的依赖关系(links-a)。

    继承和实现比较容易理解。

    • 类关系中的组合是一种完全绑定的关系,所有成员共同完成一件使命,他们的生命周期是一样的。组合体现的是非常强的整体与部分的关系,同生共死,部分不能在整体间共享。
    • 聚合是一种可拆分的整体与部分的关系,是非常松散的暂时组合,部分可以被拆出来给另一个整体。比如汽车和轮子之间的关系就是聚合关系,轮子查下来用到另一个汽车上也是完全没有问题的。
    • 依赖是出租和和聚合外的类与类之间的单向弱关系,使用另一个类的属性、方法,或以其作为方法的参数输入,或以其作为方法的返回值输出。依赖往往是模块解耦的最佳点。
    • 关联即是相互平等的依赖关系,可以在关联点上进行解耦,但是解耦难度大于依赖。

    序列化

    内存中的数据对象只有转换为二进制流才可以进行数据持久化和网络传输。将数据对象转换为二进制流的过程成为对象的序列化(Serialization)。反之,将二进制流恢复为数据对象的过程称为反序列化(Deserialization)。序列化需要保留充分的信息以恢复数据对象,但是为了节约存储空间和网络带宽,序列化的二进制流又要尽可能的小。序列化常见的使用场景是RPC框架的数据传输。常见的序列化方式有三种:

    1. Java原生序列化。Java类通过实现Serializable接口来实现类对象的序列化,这是一个标识接口。Java序列化保留了对象类的元数据(如类、成员变量、继承类信息等),以及对象数据等,兼容性最好,但不支持跨语言,而且性能一般。

    实现Serializable接口的类建议设置serialVersionUID字段值,如果不设置,在每次运行时,编译器会根据类的内部实现,包括类名、接口名、方法和属性等来自动生成serialVersionUID。如果类的源代码有修改,那么重新编译后的serialVersionUID的取值可能发生变化。因此实现Serializable接口的类一定要显式的定义serialVersionUID属性值。修改类时需要根据兼容性决定是否修改serialVersionUID值:

    • 兼容升级不需要修改serialVersionUID字段,避免反序列化失败。
    • 不兼容升级需要修改serialVersionUID字段,避免反序列化失败。

    Java原生序列化在Java反序列化时不会调用类的无参构造方法,而是调用native方法将成员变量赋值为对应类型的初始值。基于性能及兼容性考虑,不推荐使用Java原生序列化。

    1. Hessian序列化。Hessian序列化是一种支持动态类型、跨语言、基于对象传输的网络协议。Java对象序列化的二进制流还可以被其他语言(如C++、Python)反序列化。Hessian协议具有以下特性:
    • 自描述序列化类型。不依赖外部描述文件或接口定义,用一个字节表示常用基础类型,极大缩短二进制流。
    • 语言无关,支持脚本语言。
    • 协议简单,比Java原生序列化高效。

    Hessian会把复杂对象所有属性存储在一个Map中进行序列化。所以在父类、子类存在同名成员变量的情况下,Hessian序列化时,先序列化子类,再序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。

    1. JSON序列化。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。JSON序列化就是讲数据对象转换为JSON字符串。在序列化过程中抛弃了类型信息,所以反序列化时只有提供类型信息才能准确地反序列化。相比前两种方式,JSON可读性比较好,方便调试。

    序列化通常会通过网络传输对象,并且对象中常含有敏感数据。有些对象的敏感属性不需要序列化,可以通过transient关键字,避免把此属性转化为序列化的二进制流。如果一定要传递对象的灭干属性,可以使用对称和非对称加密方式独立传输,在使用某个方法把属性还原到对象中。

  • 相关阅读:
    (五)STL序列容器(deque)
    (四)STL序列容器(vector)
    (三)STL序列容器(array)
    (六)c语言之指针与函数、数组用法
    (五)c语言之内存分配
    (三)c++模板函数与函数模板详解
    Linux基础(03)gdb调试
    Linux基础(02)MakeFile的创建和使用
    Linux基础(01)开发环境的搭建
    Windows的socket编程
  • 原文地址:https://www.cnblogs.com/demo-alen/p/13547213.html
Copyright © 2011-2022 走看看