No.1

class

  简介:

    在Java中,创建一个类,就是定义一个class.。

    一个class可以包含多个字段(field),字段用来描述一个类的特征。

  补充:

    在OOP中,classinstance是“模版”和“实例”的关系。

    定义class就是定义了一种数据类型,对应的instance是这种数据类型的实例。

    通过new操作符创建新的instance,然后用变量指向它,即可通过变量来引用这个instance。

    指向instance的变量都是引用变量。

  this变量:

    在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。

    因此,通过this.field就可以访问当前实例的字段。

    如果没有命名冲突,可以省略this

    如:

class Person {
        private String name;
        public String getName() {
            return name; // 相当于this.name
        }
    }

No.2

关于参数:

  可变参数:

    可变参数用类型...定义,可变参数相当于数组类型

    如:

     private String[] names;

public void setNames(String... names) {...}

    与String[] names 方式 相比,可变参数 :

      无法传入null:

        String[] names可以传入null作为参数,而String... names 无法传入null。

        因为传入0个参数时,接收到的实际值是一个空数组而不是null

      需写在最后一列:

        参数列表中有可变参数时,可变参数必须列在最后。

  参数绑定:

    基本类型:

      基本类型参数的传递,是调用方值的复制传入参数后,双方的后续修改互不影响。

         如:"int n = 0; p.setAge(n); "执行后,n和p.age各自的修改互不影响。

    引用类型:

      引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。

      双方任意一方对这个对象进行的修改,都会影响到对方(因为指向同一个对象)。 

        如:"int[] n = {"Tom"}; p.setnums(n);" 执行后,后继对n[0]和nums[0]的修改都会影响到对方。

No.3

关于方法: 

  构造方法:

    简介:

      构造方法没有返回值(也没有void),调用构造方法必须用new操作符。

      没有定义构造方法时,编译器会自动创建一个默认的无参数构造方法。

      构造方法可以调用其他构造方法,调用语法是this(参数列表):

    初始化对象:

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

        1.先初始化字段;例如,int age = 10;表示字段初始化为10,

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

    多个构造方法:

      可以定义多个构造方法。

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

      在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分。

   方法重载(Overload):

    简介:

    在一个类中,我们可以定义多个方法。

    如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名的方法。

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

    如:

      String类提供了多个重载方法indexOf(),可以查找子串:

        int indexOf(int ch) :根据字符的Unicode码查找;

        int indexOf(String str) :根据字符串查找;

        int indexOf(int ch, int fromIndex) :根据字符查找,但指定起始位置;

        int indexOf(String str, int fromIndex) :根据字符串查找,但指定起始位置。

No.4

关于继承

  简介:  

    继承是面向对象编程中非常强大的一种机制,它可以复用代码。

     如:

      当我们让Student从Person继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。

      注意:子类自动获得了父类的所有字段,不能定义与父类重名的字段!

    Java使用extends关键字来实现继承:

      class Person {...}

      class Student extends Person {...}

    父类和子类的称呼:

      在OOP的术语中,我们把Person称为超类(super class),父类(parent class),基类(base class),

      把Student称为子类(subclass),扩展类(extended class)。

    自动继承Object类:

      在Java中,没有明确写extends的类,编译器会自动加上extends Object。

      所以,任何类,除了Object,都会继承自某个类。

      Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。

    protect:

      继承有个特点,就是子类无法访问父类的private字段或者private方法。

 

    super:

      super关键字表示父类(超类)。

      子类引用父类的字段时,可以用super.fieldName,效果等同于this.name。

      子类会自动调用父类的构造方法:

        在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。

        如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super()。

        如果父类没有默认的构造方法,子类自动调用super()时就会报错。

        这种调用意味着子类不会继承父类的任何构造函数。

    向上转型:

      简介:

        子类可以向上转型。

      如:  Person p = new Student(); // 向父类转型

        这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。

        注意到继承树是Student > Person > Object。

         向上转型实际上是把一个子类型安全地变为更加抽象的父类型。

     向下转型:

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

      如:  Object p1 = new Person();

           Person p2 = (Person) p1; //向下转型

      但是,向下转型时,父类对象不能转化为子类:

           Person p2 = new Person(); 

           Student s2 = (Student) p2; //  ClassCastException,p2是父类对象,不能转为子类

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

      如:  Object p1 = new Person();

          System.out.println( p1 instanceof Person );   // instanceof 判断,输出true

        instanceof实际上判断一个变量所指向的实例是否是指定类型,或是这个类型的子类。 

      instanceof variable:

        Java 14开始,判断instanceof后,可以直接转型为指定变量:

        如:  if (obj instanceof String s) { ...//可以直接使用s }

        使用instanceof variable这种判断并转型为指定类型变量的语法时,必须打开编译器开关--source 14和--enable-preview。

    使用组合:

      如果两个类之间是拥有和包含关系,而不是同一类别内的继承关系,那么应该使用组合。

      如: Student和Book之间应该是组合关系:class Student extends Person { protected Book book;...},既Student包含Book。

No.5

多态 Polymorphic

  覆写父类方法:

    可以在子类中覆写父类的方法。

    与重载的区别:

      如果方法签名如果不同,就是Overload,Overload的方法是一个新方法;

      如果方法签名相同,并且返回值也相同,就是Override。

    加上@Override可以让编译器帮助检查是否进行了正确的覆写。

  多态的特点:

    Java的实例方法调用是基于运行时的实际类型(即对象的类型 )的动态调用,而非变量的声明类型。

    由于这个特点,对于类似runTwice(Person p){ p.run();p.run();},只有在运行期才能动态决定调用的子类方法。

    对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。

    使用:  对于 Person[] person = {  new Person(), new Student(), new PrimaryStudent()  }

         可以定义函数 testrun(Person[] person){ for( Person p : person ) { p.run();} }

         然后调用:testrun(person);

      这样做的好处是testrun函数只需要和person数组建立联系,不需要为数组中每个对象单独编写方法。

  覆写Object方法:

    所有的class最终都继承自Object,而Object定义了几个重要的方法:

      toString():把instance输出为String;
      equals():判断两个instance是否逻辑相等;
      hashCode():计算一个instance的哈希值。

    在必要的情况下,我们可以覆写这几个方法。

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

    如:  class Person {

          protected String name;
          public String hello() { return "Hello, " + name; }
        }

        Student extends Person {
          @Override
          public String hello() {
            // 调用父类的hello()方法:
            return super.hello() + "!";
          }
        }

  final:

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

    另外,用final修饰的方法不能被Override。

    对于final字段,可以在构造方法中进行初始化:

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

    通过构造方法创建Person对象后,name字段将不可修改。

No.6

抽象类

  抽象方法:

    简介:

      如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,

      那么,可以把父类的方法声明为抽象方法

      如:

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

  抽象类:

    简介:

      如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法。

      抽象方法用abstract修饰,抽象方法无法实例化。

    作用:

      抽象类本身被设计成只能用于被继承,

      因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。

      所以,抽象方法实际上相当于定义了“规范”。

      例如,Person类定义了抽象方法run(),那么,在实现子类Student的时候,就必须覆写run()方法。

    面向抽象编程

      当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例:
      如:  Person s = new Student();
          Person t = new Teacher();
      这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型:
      如:  // 不关心Person变量的具体子类型:
          s.run();
          t.run();

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

      如:  Person e = new Employee();

          e.run();

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

      面向抽象编程的本质:

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

        不需要使用子类就可以正常编译;

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

 No.7

接口

  简介:

    抽象方法本质上是定义接口规范,

    即规定高层类的接口,从而保证所有子类都有相同的接口实现,实现多态。

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

  声明:

    在Java中,使用interface可以声明一个接口:  interface Person { void run(); }

    因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来。

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

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

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

    接口之间只能是继承(extends)关系。接口的继承相当于扩展了接口的方法。

  注意区分术语:

    Java的接口特指interface的定义,表示一个接口类型和一组方法签名,

    而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。

  设计:

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

    一般来说,公共逻辑适合放在abstract class中,

    具体逻辑放到各个子类,而接口层次代表抽象程度。

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

    因为接口比抽象类更抽象。

  定义接口的default方法:

    在接口中,可以定义default方法:

      interface Person {

        String getName();
        default void run() {
          System.out.println(getName() + " run");
        }
      }

      default相当于接口中提前实现的方法,default方法不必覆写。

      default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。

      如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。

      interface没有定义字段,所以default方法无法直接访问字段,需要通过函数才能获取。

      相比同名的default方法,子类会优先继承父类中的同名方法。

      如果实现类继承的多个接口有同名同参数的default方法,必须手动实现default方法,

      实现类可以用“接口名.super.方法名(参数)”来调用指定的default方法。

No.8

静态字段和静态方法

  静态字段:

    简介:

      在一个class中定义的字段,我们称之为实例字段。

      实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。

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

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

      静态字段属于class而不属于实例。

    静态字段的访问:

      虽然实例可以访问静态字段,但是它们指向的其实都是同一个静态字段,而实例本身没有静态字段。

      因此,不推荐用实例变量名.静态字段名去访问静态字段,而是直接使用类名.静态字段名访问。

      编译器会自动把变量名.静态字段名修改为类名.静态字段名。

    接口的静态字段:

      因为interface是一个纯抽象类,所以它不能定义实例字段。

      但是,interface是可以有静态字段的,并且静态字段必须为final类型。

      因为interface的字段只能是public static final类型,所以可以把字段前面的修饰符都省略掉。

      如:  public interface Person {

            // 编译器会自动在字段前加上public statc final
            int MALE = 1;
            int FEMALE = 2;
          }

  静态方法:

    简介:

      有静态字段,就有静态方法。用static修饰的方法称为静态方法。

      调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。

 No.9

  简介:

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

    package是一种名字空间,一个类总是属于某个包。

    “类名”只是一个简写,真正的完整类名是“包名.类名”。

    例如:  小明的Person类存放在包ming下面,因此,完整类名是ming.Person;

         小红的Person类存放在包hong下面,因此,完整类名是hong.Person;

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

  import:

    在一个class中,我们总会引用其他包的class。

    引用其他包中的类有三种写法:

      1,直接写出完整类名

      2,用import语句导入类

        可以使用*把这个包下面的所有class都导入进来,但不包括子包的class

        在导入了多个包后,类名可能会难以区分

      3,import static的语法可以导入可以导入一个类的静态字段和静态方法:

        import static java.lang.System.*;  //import static使用很少

  编译器查找包的顺序:

    Java编译器最终编译出的.class文件只使用完整类名。

    当编译器遇到一个class名称时,如果是简单类名,按下面的顺序依次查找:

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

      2,查找import的包是否包含这个class;

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

      例:  java.util.List list;   // 使用完整类名 -> java.util.List

          Format format = null;   // 使用已经import的类 -> java.text.Format
          String s = "hi";   // 使用java.lang包的String类 -> java.lang.String
          System.out.println(s);   // 使用java.lang包的System -> java.lang.System

    自动导入:

      编写class的时候,编译器会自动帮我们做两个import动作:

      1,默认自动import当前package的其他class;

      2,默认自动import java.lang.*。

    如果有两个class名称相同,例如,test.jun.Arrays和java.util.Arrays,

    那么只能import其中一个,另一个必须写完整类名。

 No.10

作用域

  简介:

    在Java中,我们经常看到public、protected、private这些修饰符。

    在Java中,这些修饰符可以用来限定访问作用域。

  public:

    定义为public的class、interface可以被其他任何类访问.

    定义为public的field、method可以被其他类访问,前提是首先有访问class的权限

  private:

    定义为private的field、method无法被其他类访问:

    private访问权限被限定在class的内部。

      推荐顺序:推荐把private方法放到public方法后面,因为public方法定义了类对外提供的功能,阅读代码的时候,应该先关注public方法

      嵌套类的访问权限:如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问private的权限。

  protected:

    protected作用于继承关系。

    定义为protected的字段和方法可以被子类访问,以及子类的子类

  package:

    package的作用域是指:一个类允许访问同一个package的没有public、private修饰的class,

      以及没有public、protected、private修饰的字段和方法

    只要在同一个包(完整包名相同),就可以访问package权限的class、field和method。

  作用域的使用:

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

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

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

No.11

classpath和jar

  classpath简介:

    classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。

    因为Java是编译型语言,源码文件是.java,而编译后的.class文件才是真正可以被JVM执行的字节码。

    因此,JVM需要知道,要加载指定的类,应该去哪搜索对应的.class文件

    所以,classpath就是一组目录的集合。

    classpath设置的搜索路径与操作系统相关。

    例如: 在Windows系统上,用 ; 分隔,带空格的目录用 "" 括起来:

        .;C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"

        在Linux系统上,用:分隔。

        /usr/shared:/usr/local/bin:/home/liaoxuefeng/bin

    假设classpath是.;C:\work\project1\bin;C:\shared,( .代表当前目录。)

      当JVM在加载abc.xyz.Hello这个类时,会依次查找:

      <当前目录>\abc\xyz\Hello.class

      C:\work\project1\bin\abc\xyz\Hello.class

      C:\shared\abc\xyz\Hello.class

    classpath:

      设置:

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

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

      启动时设置:

        启动JVM时设置classpath,实际上就是给java命令传入-classpath + 路径:

        如:  java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello

            (-classpath 可以简写为 -cp)

      未设置:

        如果没有设置系统环境变量,也没有传入-cp参数,如java abc.xyz.Hello

        那么JVM默认的classpath为.,即当前目录

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

      JVM自带的核心类库(rt.jar)不需要设置。

  jar包:

    简介:

      如果有很多.class文件,散落在各层目录中。

      把目录打一个包,变成一个文件,更方便管理。

      jar包就这样的文件。它可以把处于package组织下的目录层级,以及各级目录下的所有文件都打成一个jar文件。

      这样一来,无论是备份,还是发给客户,就简单多了。

    jar包的的结构:

      jar包实际上就是一个zip格式的压缩文件,而jar包相当于目录。

      如果我们要执行一个jar包内的class,就可以把jar包放到classpath中:

      java -cp ./hello.jar abc.xyz.Hello  // 当前目录下的hello.jar文件

      这样JVM会自动在hello.jar文件里去搜索这个类(Hello.class)。

    jar包的创建:

      将文件以zip格式压缩,然后,把后缀从.zip改为.jar,一个jar包就创建成功。

       需要特别注意的是,jar包里的第一层目录,不能是bin 。

    MANIFEST.MF:

      jar包还可以包含一个特殊的/META-INF/MANIFEST.MF文件,

      MANIFEST.MF是纯文本,可以指定Main-Class和其它信息。

      JVM会自动读取这个MANIFEST.MF文件,

      如果配置了Main-Class,就可以用更方便的命令执行:

      java -jar hello.jar

      jar包还可以包含其它jar包,这个时候,就需要在MANIFEST.MF文件里配置classpath了。

      Java社区提供了大量的开源构建工具,例如Maven,使用它们可以非常方便地创建jar包。

  总结:

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

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

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

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