廖雪峰java教程学习笔记——面向对象基础

从C++到一点点Scala,又学了一点Python,再回到Java。

互联网时代,Java基础还是得打打牢。从java web到大数据,都离不开它。

学习廖雪峰的课程,速刷一遍。每天更新一篇笔记。

运行java程序执行java classname

比如有Hello.class,执行java Hello即可;若执行java Hello.class则会报错。

Java 中的length 、length()、size()

length :
Arrays (int[], double[], String[]) — 取得Array的长度

length():
String related Object (String, StringBuilder, etc) — 取得字符串长度

size():
Collection Object (ArrayList, Set, etc) — 取得集合对象大小

可变参数

class Group {
    private String[] names;

    public void setNames(String... names) { //用类型...定义,相当于数组类型:
        this.names = names;
    }
}

详见:https://www.liaoxuefeng.com/wiki/1252599548343744/1260452774408320

变量没有冲突时this可省略

class Person {
    private String name;

    public String getName() {
        return name; // 如果没有命名冲突,可以省略this。相当于this.name
    }
}

class前修饰符,加public和不加public的区别

加public表示全局类,该类可以import到任何类内。

不加public默认为保留类,只能被同一个包内的其他类引用。

继承

用extends关键字:

class Student extends Person {
    // 不要重复name和age字段/方法,
    // 只需要定义新增score字段/方法:
    private int score;

    public int getScore() {}
    public void setScore(int score) {}
}

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

Java只允许一个class继承自一个类(你只有一个亲爹!),一个父类可以有多个子类。默认继承自object类,只有Object没有父类

       ┌───────────┐
       │  Object   │
       └───────────┘
             ▲
             │
       ┌───────────┐
       │  Person   │
       └───────────┘
          ▲     ▲
          │     │
          │     │
┌───────────┐ ┌───────────┐
│  Student  │ │  Teacher  │
└───────────┘ └───────────┘

protected关键字,允许子类访问父类的字段。

class Person {
    protected String name;
    protected int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // OK!
    }
}

super()

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

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

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age); // 调用父类的构造方法Person(String, int)
        this.score = score;
    }
}

向上转型(upcasting)

子类变父类,完全ok,因为子类肯定有父类所有功能:

Person p = new Student(); // 可以的,父类声明可以指向一个子类实例

向下转型(downcasting)

父类变子类,失败;子类向上转成的父类,再转回子类,则OK!(反复横跳,结果挺好)

Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok,p1本来就是子类,只不过向上转成了父类
Student s2 = (Student) p2; // runtime error! ClassCastException!

在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。

为了避免向下转型出错,可以用instanceof操作符:

Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}

多态 Polymorphic

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

public class Main {
    public static void main(String[] args) {
        // 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
        Income[] incomes = new Income[] {
            new Income(3000),
            new Salary(7500),
            new StateCouncilSpecialAllowance(15000)
        };
        System.out.println(totalTax(incomes));
    }

    public static double totalTax(Income... incomes) {
        double total = 0;
        for (Income income: incomes) {
            total = total + income.getTax();
        }
        return total;
    }
}

利用多态,totalTax()方法只需要和Income打交道,它完全不需要知道Salary和StateCouncilSpecialAllowance的存在,就可以正确计算出总的税。

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

final

可用于类或方法前,表示禁止继承!即不能被override。

也可用于字段前,表示禁止修改。

抽象类

如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法,然后必须把Person类本身也声明为abstract,才能正确编译它:

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

抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。

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

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

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

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

总是拿抽象类调方法即可。

接口

如果一个抽象类没有字段,所有方法全部都是抽象方法:

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

就可以把该抽象类改写为接口:interface

interface Person {
    void run();
    String getName();
}

public abstract这两个修饰符不需要写出来(写不写效果都一样)。

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

class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(this.name + " run");
    }

    @Override
    public String getName() {
        return this.name;
    }
}

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

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

抽象类和接口对比:
在这里插入图片描述
接口可以继承另一个接口,使用extends

interface Hello {
    void hello();
}

interface Person extends Hello { //Person默认带有hello()抽象方法
    void run();
    String getName();
}

接口可以有default方法:

interface Person {
    String getName();
    default void run() { //子类可以选择不实现该方法
        System.out.println(getName() + " run");
    }
}

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

静态字段和静态方法

静态字段

用static修饰的字段,称为静态字段:static field。

它们不属于某个实例,而是属于这个类的!

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



class Person {
    public String name;
    public int age;
    // 定义静态字段number:
    public static int number;
}

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

推荐用类名来访问静态字段。可以把静态字段理解为描述class本身的字段(非实例字段)。对于上面的代码,更好的写法是:

Person.number = 99;
System.out.println(Person.number);
静态方法

同样直接可以拿类名去调用,不用实例化。

public class Main {
    public static void main(String[] args) {
        Person.setNumber(99);
        System.out.println(Person.number);
    }
}

class Person {
    public static int number;

    public static void setNumber(int value) {
        number = value;
    }
}

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

静态的属性和方法在程序启动的时候,就全部装入内存的,而不管这些方法、属性以后有没有用到。

静态方法经常用于工具类。例如:

Arrays.sort()

Math.random()

静态方法也经常用于辅助方法。注意到Java程序的入口main()也是静态方法。

练习

给Person类增加一个静态字段count和静态方法getCount,统计实例创建的个数。

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(p.getCount());
        p = new Person();
        System.out.println(p.getCount());
    }
}

class Person {
    public static int count = 0;
    public Person(){
        count++;
    }
    int getCount(){
        return count;
    }
}

打印:

1
2
接口静态字段

interface虽热不能有实例字段,但可以有静态字段,并且静态字段必须为final类型:

public interface Person {
    public static final int MALE = 1;
    public static final int FEMALE = 2;
}

可以把public static final去掉,编译器默认就加上了:

public interface Person {
    // 编译器会自动加上public statc final:
    int MALE = 1;
    int FEMALE = 2;
}

接口静态字段必须给默认值。

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

要特别注意:包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。

编译后的.class文件也需要按照包结构存放:
在这里插入图片描述
位于同一个包的类,可以访问包作用域的字段和方法。不用public、protected、private修饰的字段和方法就是包作用域。

导入包的方法:

  1. 用全称:mr.jun.Arrays arrays = new mr.jun.Arrays();
  2. importimport mr.jun.Arrays; 然后写Arrays arrays即可;
  3. 可以import *,但不推荐,因为导入包太多后,就分不清类到底属于哪个了;
  4. import static,导入一个类的静态字段和静态方法,import static java.lang.System.*;,即导入了System类的所有静态字段和静态方法;
Java编译器检查类的顺序

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

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

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

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

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

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

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

编译器默认的两个import
  1. 默认自动import当前package的其他class;
  2. 默认自动import java.lang.*。
最佳实践
  1. 推荐的做法是使用倒置的域名来确保唯一性,如:com.liaoxuefeng.sample
  2. 要注意不要和java.lang包的类重名,即自己的类不要使用这些名字:
    String
    System
    Runtime

    也不要和JDK常用类重名:
    java.util.List
    java.text.Format
    java.math.BigInteger

作用域

Java内建的访问权限包括public、protected、private和package权限;

包作用域

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

final
  1. final class,防止类被继承;
  2. protected final void hi(),防止方法被覆写;
  3. private final int n = 0, 防止字段被重新赋值;
  4. final int t 参数,防止局部变量t被重新赋值;
嵌套类

定义在一个class内部的class称为嵌套类(nested class),嵌套类拥有访问private的权限。

public class Main {
    public static void main(String[] args) {
        Inner i = new Inner();
        i.hi();
    }

    // private方法:
    private static void hello() {
        System.out.println("private hello!");
    }

    // 静态内部嵌套类:
    static class Inner {
        public void hi() {
            Main.hello();  //访问private方法
        }
    }
}
最佳实践
  1. 如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。

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

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

内部类Inner Class

普通内部类 Inner Class
class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

要实例化一个Inner,我们必须首先创建一个Outer的实例,然后,调用Outer实例的new来创建Inner实例:

Outer.Inner inner = outer.new Inner();

因为Inner Class除了有一个this指向它自己,还隐含地持有一个Outer Class实例,可以用Outer.this访问这个实例。所以,实例化一个Inner Class不能脱离Outer实例。

Inner Class和普通Class相比,除了能引用Outer实例外,还有一个额外的“特权”,就是可以修改Outer Class的private字段,因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法。

观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class。

匿名类 Anonymous Class

属于内部类的一种。这个有点难懂。于是参考了菜鸟教程,发现讲的更清晰点:
https://www.runoob.com/java/java-anonymous-class.html

class outerClass {

    // 定义一个匿名类
    object1 = new Type(parameterList) {
         // 匿名类代码
    };
}

以上的代码创建了一个匿名类对象 object1,匿名类是表达式形式定义的,所以末尾以分号 ; 来结束。
在这里插入图片描述

通常继承一个父类或实现一个接口:

class Polygon {
   public void display() {
      System.out.println("在 Polygon 类内部");
   }
}

class AnonymousDemo {
   public void createClass() {

      // 创建的匿名类继承了 Polygon 类
      Polygon p1 = new Polygon() {
         public void display() {
            System.out.println("在匿名类内部。");
         }
      };
      p1.display();
   }
}

class Main {
   public static void main(String[] args) {
       AnonymousDemo an = new AnonymousDemo();
       an.createClass();
   }
}

到这有点懂了,这有点像lambda表达式,不关心名称,只关心结果。

静态内部类 Static Nested Class

最后一种内部类。

用static修饰的内部类和Inner Class有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this,但它可以访问Outer的private静态字段和静态方法。如果把StaticNested移到Outer之外,就失去了访问private的权限。

内部类小结

Java的内部类可分为Inner Class、Anonymous Class和Static Nested Class三种:

Inner Class和Anonymous Class本质上是相同的,都必须依附于Outer Class的实例,即隐含地持有Outer.this实例,并拥有Outer Class的private访问权限;

Static Nested Class是独立类,但拥有Outer Class的private访问权限。

classpath

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

classpath的设定方法有三种:

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

  2. 在启动JVM时设置classpath变量,推荐:
    java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello
    或者使用-cp的简写:
    java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello

  3. 不用前两种方法,默认为.,即当前目录。默认的当前目录.对于绝大多数情况都够用了。

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

jar包 Java Archive

jar包可以把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件,这样一来,无论是备份,还是发给客户,就简单多了。

jar包相当于一个目录。如果我们要执行一个jar包的class,就可以把jar包放到classpath中:

java -cp ./hello.jar abc.xyz.Hello

这样JVM会自动在hello.jar文件里去搜索某个类。

jar包实际上就是一个zip格式的压缩文件。可以手动把一组类打包成zip文件,再把后缀改为.jar,一个jar包就创建成功。

META-INF

大多数 JAR 文件包含一个 META-INF 目录,它用于存储包和扩展的配置数据,如安全性和版本信息。Java 2 平台(标准版【J2SE】)识别并解释 META-INF 目录中的下述文件和目录,以便配置应用程序、扩展和类装载器:

  • MANIFEST.MF:可以指定Main-Class和其它信息。JVM会自动读取MANIFEST.MF文件。如果存在Main-Class,就可不必在命令行指定启动的类名,而是用更方便的命令:java -jar hello.jar
    jar包还可以包含其它jar包,这个时候,就需要在MANIFEST.MF文件里配置classpath了。
    在大型项目中,不可能手动编写MANIFEST.MF文件,再手动创建zip包。Java社区提供了大量的开源构建工具,例如Maven,可以非常方便地创建jar包。
  • 通过 MAVEN 插件打包进来的文件比如:
maven
services : 存储所有服务提供程序配置文件
  • 还有一些不常看到的:
INDEX.LIST :这个文件由 jar工具的新选项 -i生成,它包含在应用程序或者扩展中定义的包的位置信息。它是 JarIndex 实现的一部分,并由类装载器用于加速类装载过程。
.SF:这是 JAR 文件的签名文件
.DSA:与签名文件相关联的签名程序块文件,它存储了用于签名 JAR 文件的公共签名。
LICENSE.txt :证书信息
NOTICE.txt : 公告信息

参考:聊一聊 JAR 文件和 MANIFEST.MF

模块 Module

jar只是用于存放class的容器,它并不关心class之间的依赖。

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

为了表明Java模块化的决心,从Java 9开始,原有的Java标准库已经由一个单一巨大的rt.jar分拆成了几十个模块,这些模块以.jmod扩展名标识,可以在$JAVA_HOME/jmods目录下找到它们:

java.base.jmod
java.compiler.jmod
java.datatransfer.jmod
java.desktop.jmod
...

这些.jmod文件每一个都是一个模块,模块名就是文件名。例如:模块java.base对应的文件就是java.base.jmod。模块之间的依赖关系已经被写入到模块内的module-info.class文件了。所有的模块都直接或间接地依赖java.base模块,只有java.base模块不依赖任何模块,它可以被看作是“根模块”,好比所有的类都是从Object直接或间接继承而来。

把一堆class封装为jar仅仅是一个打包的过程,而把一堆class封装为模块则不但需要打包,还需要写入依赖关系,并且还可以包含二进制代码(通常是JNI扩展)。此外,模块支持多版本,即在同一个模块中可以为不同的JVM提供不同的版本。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值