zoukankan      html  css  js  c++  java
  • Java面向对象基础知识笔记:方法、构造方法、方法重载、继承、多态、抽象类、接口、静态字段与静态方法、包、作用域、classpath与jar、模块依赖关系

    一、方法

    1、直接把fieldpublic暴露给外部可能会破坏封装性,直接操作field,容易造成逻辑混乱。为了避免外部代码直接去访问field,我们可以用private修饰field,拒绝外部访问。

    2、privatefield,我们需要使用方法(method)来让外部代码可以间接修改field

      虽然外部代码不能直接修改private字段,但是,外部代码可以调用方法setName()setAge()来间接修改private字段。

      在方法内部,我们就有机会检查参数对不对。

      同样,外部代码不能直接读取private字段,但可以通过getName()getAge()间接获取private字段的值。

      所以,一个类通过定义方法,就可以给外部代码暴露一些操作的接口,同时,内部自己保证逻辑一致性。

    3、定义方法的语法是:

    修饰符 方法返回类型 方法名(方法参数列表) {
        若干方法语句;
        return 方法返回值;
    }

      方法返回值通过return语句实现,如果没有返回值,返回类型设置为void,可以省略return

    4、private方法:

      有public方法,自然就有private方法。和private字段一样,private方法不允许外部调用,那我们定义private方法有什么用?

      定义private方法的理由是内部方法是可以调用private方法的。

      我们还注意到,这个Person类只定义了birth字段,没有定义age字段,获取age时,通过方法getAge()返回的是一个实时计算的值,并非存储在某个字段的值。这说明方法可以封装一个类的对外接口,调用方不需要知道也不关心Person实例在内部到底有没有age字段。

    5、this变量

      在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。

      如果没有命名冲突,可以省略this。但是,如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this

    6、方法参数、可变参数。

    7、理解Java里为什么只有值传递。

    二、构造方法

      创建实例的时候,实际上是通过构造方法来初始化实例的。实例在创建时通过new操作符会调用其对应的构造方法,构造方法用于初始化实例。

      由于构造方法是如此特殊,所以构造方法的名称就是类名。构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。

    1、默认构造方法:如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句。

      要特别注意的是,如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法。

      如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来

    2、类的字段的默认值:

      没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false

      也可以直接在声明字段的时候就对字段直接进行初始化。

    3、既对字段进行初始化,又在构造方法中对字段进行初始化:

    当我们创建对象的时候,new Person("Xiao Ming", 12)得到的对象实例,字段的初始值是啥?

      在Java中,创建对象实例的时候,按照如下顺序进行初始化:

    (1)先初始化字段,例如,int age = 10;表示字段初始化为10double salary;表示字段默认初始化为0String name;表示引用类型字段默认初始化为null

    (2)执行构造方法的代码进行初始化。

      因此,构造方法的代码由于后运行,所以,new Person("Xiao Ming", 12)的字段值最终由构造方法的代码确定。

    4、多构造方法

      可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分判断该调用哪个构造方法。

      可以在一个构造方法内部调用另一个构造方法,便于代码复用。

    三、方法重载

      在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。这种方法名相同,但各自的参数不同,称为方法重载(Overload)。

      注意:方法重载的返回值类型通常都是相同的。

      方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。

    四、继承

      继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。当我们让StudentPerson继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。

      Java使用extends关键字来实现继承。通过继承,Student只需要编写额外的功能,不再需要重复代码。

    1、子类自动获得了父类的所有字段,严禁定义与父类重名的字段!

    2、继承树:任何类,除了Object,都会继承自某个类。一般类默认继承Object。

    3、Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。

    4、protected字段

      继承有个特点,就是子类无法访问父类的private字段或者private方法。这使得继承的作用被削弱了。

      为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问。

      protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问。

    5、super关键字:super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName

    6、在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();

      如果父类Person类并没有无参数的构造方法,就会编译失败。

      解决方法是调用Person类存在的某个构造方法。例如:super(name, age); // 调用父类的构造方法Person(String, int)

    7、如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。

      这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

    8、阻止继承

      正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。

    // 例如,定义一个Shape类:
    public sealed class Shape permits Rect, Circle, Triangle {
    }
    
    // 上述Shape类就是一个sealed类,它只允许指定的3个类继承它。如果写:
    public final class Rect extends Shape {...}
    
    // 是没问题的,因为Rect出现在Shape的permits列表中。但是,如果定义一个Ellipse就会报错:
    public final class Ellipse extends Shape {...}
    // Compile error: class is not allowed to extend sealed class: Shape
    // 原因是Ellipse并未出现在Shape的permits列表中。这种sealed类主要用于一些框架,防止继承被滥用。

    9、向上转型:如果Student是从Person继承下来的,那么,一个引用类型为Person的变量,是可以指向Student类型的实例的。

      这是因为Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的!这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。

      向上转型实际上是把一个子类型安全地变为更加抽象的父类型。(比如:继承树是Student 继承 Person 继承 Object,所以,可以把Student类型转型为Person,或者更高层次的Object。)

    10、向下转型:和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。

      向下转型很可能会失败,因为子类功能比父类多,多的功能无法凭空变出来。失败的时候,Java虚拟机会报ClassCastException

      为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型。

    11、区分继承和组合 —— 继承是is关系,组合是has关系。

      如 Book类有name字段,Person类也有name字段。那么,我们能不能让Student继承自Book呢?显然,从逻辑上讲,这是不合理的,Student不应该从Book继承,而应该从Person继承。

      究其原因,是因为StudentPerson的一种,它们是is关系,而Student并不是Book。实际上StudentBook的关系是has关系。具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例。

    class Student extends Person {
        protected Book book;  // 学生有一本书:组合关系
        protected int score;
    }

    12、小结:继承是面向对象编程的一种强大的代码复用方式;

      Java只允许单继承,所有类最终的根类是Object

      protected允许子类访问父类的字段和方法;

      子类的构造方法可以通过super()调用父类的构造方法;

      可以安全地向上转型为更抽象的类型;

      可以强制向下转型,最好借助instanceof判断;

      子类和父类的关系是is,has关系不能用继承。

    五、多态

      Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型,这就是Java里的多态。

    1、在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。

    2、Override和Overload不同的是,如果方法签名如果不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override

      注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错。

    3、多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。

    4、所以多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。

      这种不确定性的方法调用,究竟有什么作用?  ——  针对不同人,报税的例子。

      观察totalTax()方法:利用多态,totalTax()方法只需要和Income打交道,它完全不需要知道SalaryStateCouncilSpecialAllowance的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从Income派生,然后正确覆写getTax()方法就可以。把新的类型传入totalTax(),不需要修改任何代码。

      可见,多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

    5、调用super:在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。

    6、final关键字阻止覆写 —— 用final修饰的方法不能被Override

      继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final

      如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承。

      对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。可以在构造方法中初始化final字段:

    class Person {
        public final String name;
        public Person(String name) {
            this.name = name;
        }
    }

      这种方法更为常用,因为可以保证实例一旦创建,其final字段就不可修改。

    7、小结:

      子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;

      Java的方法调用总是作用于运行期对象的实际类型,这种行为称为多态;

      final修饰符有多种作用:

    (1)final修饰的方法可以阻止被覆写;

    (2)final修饰的class可以阻止被继承;

    (3)final修饰的field必须在创建对象时初始化,随后不可修改。

    六、抽象类

    1、由于多态的存在,每个子类都可以覆写父类的方法。从Person类派生的StudentTeacher都可以覆写run()方法。如果父类Personrun()方法没有实际意义,能否去掉方法的执行语句?答案是不行,会导致编译错误,因为定义方法的时候,必须实现方法的语句。能不能去掉父类的run()方法?答案还是不行,因为去掉父类的run()方法,就失去了多态的特性。

    2、如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法。把一个方法声明为abstract,表示它是一个抽象方法,本身没有实现任何方法语句。

      因为这个抽象方法本身是无法执行的,所以,Person类也无法被实例化。编译器会告诉我们,无法编译Person类,因为它包含抽象方法。必须把Person类本身也声明为abstract,才能正确编译它:

    abstract class Person {
        public abstract void run();
    }

    3、抽象方法:如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。

    4、抽象类:因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。使用abstract修饰的类就是抽象类。

    5、我们无法实例化一个抽象类,会报编译错误。那么无法实例化的抽象类有什么用?

      因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。例如,Person类定义了抽象方法run(),那么,在实现子类Student的时候,就必须覆写run()方法。

    6、面向抽象编程

      当我们定义了抽象类Person,以及具体的StudentTeacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例

    Person s = new Student();
    Person t = new Teacher();

      这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型:

    // 不关心Person变量的具体子类型:
    s.run();
    t.run();

      同样的代码,如果引用的是一个新的子类,我们仍然不关心具体类型:

    // 同样不关心新的子类是如何实现run()方法的:
    Person e = new Employee();
    e.run();

      这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程

      面向抽象编程的本质就是:

    (1)上层代码只定义规范(例如:abstract class Person);

    (2)不需要子类就可以实现业务逻辑(正常编译);

    (3)具体的业务逻辑由不同的子类实现,调用者并不关心。

    七、接口

      在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。

      如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口:interface

    1、在Java中,使用interface可以声明一个接口。所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。

    2、当一个具体的class去实现一个interface时,需要使用implements关键字。

    3、在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface

    class Student implements Person, Hello { // 实现了两个interface
        ...
    }

    4、注意区分术语:Java的接口特指interface的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。

    5、抽象类和接口的对比如下:

    6、接口继承:一个interface可以继承自另一个interfaceinterface继承自interface使用extends,它相当于扩展了接口的方法。

    7、继承关系

      合理设计interfaceabstract class的继承关系,可以充分复用代码。

      一般来说,公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。可以参考Java的集合类定义的一组接口、抽象类以及具体子类的继承关系:

      在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象。

    List list = new ArrayList(); // 用List接口引用具体子类的实例
    Collection coll = list; // 向上转型为Collection接口
    Iterable it = coll; // 向上转型为Iterable接口

    8、default方法:

      实现类可以不必覆写default方法。

      default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。

      default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。

    9、小结:

      Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;

      接口也是数据类型,适用于向上转型和向下转型;

      接口的所有方法都是抽象方法,接口不能定义实例字段;

      接口可以定义default方法(JDK>=1.8)。

    八、静态字段与静态方法

    1、实例字段:在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。

    2、静态字段:还有一种字段,是用static修饰的字段,称为静态字段:static field

    3、实例字段与静态字段区别:实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。

    4、对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例。

    5、虽然实例可以访问静态字段,但是它们指向的其实都是Person class的静态字段。所以,所有实例共享一个静态字段

    6、因此不推荐用实例变量.静态字段去访问静态字段,因为在Java程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段只是因为编译器可以根据实例类型自动转换为类名.静态字段来访问静态对象。

      推荐用类名来访问静态字段。可以把静态字段理解为描述class本身的字段(非实例字段)

    7、静态方法:用static修饰的方法称为静态方法。 调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。

    8、因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。

      通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。

    9、接口的静态字段:因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型。

      实际上,因为interface的字段只能是public static final类型,所以我们可以把这些修饰符都去掉,编译器会自动把该字段变为public static final类型。

    10、小结:

      静态字段属于所有实例“共享”的字段,实际上是属于class的字段;

      调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法;

      静态方法常用于工具类和辅助方法。

    九、包

      在Java中,我们使用package来解决名字冲突。

    1、Java定义了一种名字空间,称之为包:package。一个类总是属于某个包,类名(比如Person)只是一个简写,真正的完整类名是包名.类名

      包可以是多层结构,用.隔开。例如:java.util

    2、在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。

    3、包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。

      没有定义包名的class,它使用的是默认包,非常容易引起名字冲突,因此,不推荐不写包名的做法。

    4、包作用域:位于同一个包的类,可以访问包作用域的字段和方法

      不用publicprotectedprivate修饰的字段和方法就是包作用域。例如,Person类定义在hello包下面:

    package hello;
    public class Person {
        // 包作用域:
        void hello() {
            System.out.println("Hello!");
        }
    }

    5、import包 —— 在一个class中,我们总会引用其他的class,三种方式

      第一种,直接写出完整类名;

      第二种写法是用import语句,导入完整类名,然后写简单类名

      第三种写法:import的时候,可以使用*,表示把这个包下面的所有class都导入进来(但不包括子包的class

      还有一种import static的语法,它可以导入可以导入一个类的静态字段和静态方法,很少使用。

    6、类的查找规则

      Java编译器最终编译出的.class文件只使用完整类名,因此,在代码中,当编译器遇到一个class名称时:

    (1)如果是完整类名,就直接根据完整类名查找这个class

    (2)如果是简单类名,按下面的顺序依次查找:

      查找当前package是否存在这个class

      查找import的包是否包含这个class

      查找java.lang包是否包含这个class

      如果按照上面的规则还无法确定类名,则编译报错。

    7、小结

      Java内建的package机制是为了避免class命名冲突;

      JDK的核心类使用java.lang包,编译器会自动导入;

      JDK的其它常用类定义在java.util.*java.math.*java.text.*,……;

      包名推荐使用倒置的域名,例如org.apache

    十、作用域

      在Java中,我们经常看到publicprotectedprivate这些修饰符。在Java中,这些修饰符可以用来限定访问作用域。

    1、public:

      定义为publicclassinterface可以被其他任何类访问。

      定义为publicfieldmethod可以被其他类访问,前提是首先有访问class的权限。

    2、private:定义为privatefieldmethod无法被其他类访问。

      确切地说,private访问权限被限定在class的内部,而且与方法声明顺序无关。推荐把private方法放到后面,因为public方法定义了类对外提供的功能,阅读代码的时候,应该先关注public方法。

      由于Java支持嵌套类,如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问private的权限

    3、protected

      protected作用于继承关系。定义为protected的字段和方法可以被子类访问,以及子类的子类。

    4、package:只要在同一个包,就可以访问package权限的classfieldmethod

      包作用域是指一个类允许访问同一个package的没有publicprivate修饰的class,以及没有publicprotectedprivate修饰的字段和方法。

    5、局部变量

      在方法内部定义的变量称为局部变量,局部变量作用域从变量声明处开始到对应的块结束。方法参数也是局部变量。

      使用局部变量时,应该尽可能把局部变量的作用域缩小,尽可能延后声明局部变量。

    6、final修饰符:Java还提供了一个final修饰符。final与访问权限不冲突,它有很多作用。

      用final修饰class可以阻止被继承;

      用final修饰method可以阻止被子类覆写;

      用final修饰field可以阻止被重新赋值;

      用final修饰局部变量可以阻止被重新赋值;

    7、最佳实践

      如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。

      把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。

      一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。

    8、小结:

      Java内建的访问权限包括publicprotectedprivatepackage权限;

      Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;

      final修饰符不是访问权限,它可以修饰classfieldmethod

      一个.java文件只能包含一个public类,但可以包含多个非public类。

    十一、classpath 和 jar

    1、到底什么是classpath? —— classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class

      因为Java是编译型语言,源码文件是.java,而编译后的.class文件才是真正可以被JVM执行的字节码。因此,JVM需要知道,如果要加载一个abc.xyz.Hello的类,应该去哪搜索对应的Hello.class文件。

    2、classpath就是一组目录的集合,它设置的搜索路径与操作系统相关。

    // 在Windows系统上,用;分隔,带空格的目录用""括起来,可能长这样:
    C:workproject1in;C:shared;"D:My Documentsproject1in"
    
    // 在Linux系统上,用:分隔,可能长这样:
    /usr/shared:/usr/local/bin:/home/liaoxuefeng/bin

      现在我们假设classpath是  .;C:workproject1in;C:shared  ,当JVM在加载abc.xyz.Hello这个类时,会依次查找:

    • <当前目录>abcxyzHello.class

    • C:workproject1inabcxyzHello.class

    • C:sharedabcxyzHello.class

      注意到  .代表当前目录。如果JVM在某个路径下找到了对应的class文件,就不再往后继续搜索。如果所有路径下都没有找到,就报错。

    3、classpath的设定方法有两种:

      在系统环境变量中设置classpath环境变量,不推荐;

      在启动JVM时设置classpath变量,推荐。

      我们强烈不推荐在系统环境变量中设置classpath,那样会污染整个系统环境。在启动JVM时设置classpath才是推荐的做法。实际上就是给java命令传入-classpath-cp参数:

    java -classpath .;C:workproject1in;C:shared abc.xyz.Hello
    
    // 或者使用-cp的简写:
    java -cp .;C:workproject1in;C:shared abc.xyz.Hello

      没有设置系统环境变量,也没有传入-cp参数,那么JVM默认的classpath.,即当前目录:告诉JVM只在当前目录搜索Hello.class

    java abc.xyz.Hello

    4、在IDE中运行Java程序,IDE自动传入的-cp参数是当前工程的bin目录和引入的jar包。

      通常,我们在自己编写的class中,会引用Java核心库的class,例如,StringArrayList等。这些class应该上哪去找?有很多“如何设置classpath”的文章会告诉你把JVM自带的rt.jar放入classpath,但事实上,根本不需要告诉JVM如何去Java核心库查找class,JVM怎么可能笨到连自己的核心库在哪都不知道?

      不要把任何Java核心库添加到classpath中!JVM根本不依赖classpath加载核心库!更好的做法是,不要设置classpath!默认的当前目录.对于绝大多数情况都够用了。

    5、jar包

      如果有很多.class文件,散落在各层目录中,肯定不便于管理。如果能把目录打一个包,变成一个文件,就方便多了。jar包就是用来干这个事的,它可以把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件,这样一来,无论是备份,还是发给客户,就简单多了。

    6、jar包实际上就是一个zip格式的压缩文件,而jar包相当于目录。如果我们要执行一个jar包的class,就可以把jar包放到classpath中:

    java -cp ./hello.jar abc.xyz.Hello
    // 这样JVM会自动在hello.jar文件里去搜索某个类。

    7、小结:

      JVM通过环境变量classpath决定搜索class的路径和顺序;

      不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;

      jar包相当于目录,可以包含很多.class文件,方便下载和使用;

      MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。

    十二、模块

    1、.class文件是JVM看到的最小可执行文件,而一个大型程序需要编写很多Class,并生成一堆.class文件,很不便于管理,所以,jar文件就是class文件的容器。

      如果漏写了某个运行时需要用到的jar,那么在运行期极有可能抛出ClassNotFoundException。所以,jar只是用于存放class的容器,它并不关心class之间的依赖。

    2、从Java 9开始引入的模块,主要是为了解决“依赖”这个问题。如果a.jar必须依赖另一个b.jar才能运行,那我们应该给a.jar加点说明啥的,让程序在编译和运行的时候能自动定位到b.jar,这种自带“依赖关系”的class容器就是模块

      模块的重要作用就是声明依赖关系。

    3、小结:

      Java 9引入的模块目的是为了管理依赖;

      使用模块可以按需打包JRE;

      使用模块对类的访问权限有了进一步限制。

  • 相关阅读:
    Win10安装系统后,提示initializing and establishing link无法启动系统
    CSVN使用入门
    CSVN(SVN)命令入门及使用过程中遇到的错误问题汇总
    CSVN部署安装,实现web管理svn
    xenserver使用快照创建虚拟机,提示eth0 has different mac错误
    开机或者安装系统时提示tsc: Fast TSC calibration failed解决方法
    Nginx——debug的使用
    nginx屏蔽某一ip的访问
    plsql快速入门
    Web02_HTML&CSS
  • 原文地址:https://www.cnblogs.com/goloving/p/14807841.html
Copyright © 2011-2022 走看看