第二部分 面向对象

chapter3 类的基础

static表示类方法,也叫静态方法,与类方法相对的是实例方法。
Java编译器可以对final变量进行一些特别的优化。
表示类变量的时候,static修饰符是必须的,但public和final都不是必须的。

在实例方法中,有一个隐含的参数,这个参数就是当前操作的实例自己,直接操作实例变量,实际也需要通过参数进行。

  • 类方法只能访问类变量,不能访问实例变量,可以调用其他的类方法,不能调用实例方法。
  • 实例方法既能访问实例变量,也能访问类变量,既可以调用实例方法,也可以调用类方法。

初始化代码块

int x = 1;
int y;
{
    y = 2;
}

静态初始化代码块

static int STATIC_TWO;
static{
   STATIC_TWO = 2;
}

静态初始化代码块在类加载的时候执行,这是在任何对象创建之前,且执行一次。

构造方法

  • 名称是固定的,与类名相同。
  • 没有返回值,也不能有返回值。构造方法隐含的返回值就是实例本身。
  • 构造方法是用于初始化对象的,如果要调用别的构造方法,先调别的,然后根据情况自己再做调整。

私有构造方法

  1. 不能创建类的实例,类只能被静态访问
  2. 能创建类的实例,但只能被类的静态方法调用。
  3. 只是用来被其他多个构造方法调用,用于减少重复代码。

带完整包名的类名称为其完全限定名

包声明语句应该位于源代码的最前面,前面不能有注释外的其他语句。

import java.util.*;

这个引入不能递归,它只会引入java.util包下的直接类,而不会引入java.util下嵌套包内的类。

包的可见性范围就是同一个包内,同一个包内的其他类可以访问,而其他包内的类不可以访问。同一个包指的是同一个直接包,子包下的类并不能访问。

可见性范围从小到大是:

private<默认(包)<protected<public

打包jar包

jar -cvf <包名>.jar <最上层包名>

import是编译时概念,用于完全限定名,在运行时,只根据完全限定名寻找并加载类

chapter4 类的继承

  • Java使用extends关键字表示继承关系,一个类最多只能有一个父类;
  • 子类不能直接访问父类的私有属性和方法。
  • 除了私有的外,子类继承了父类的其他属性和方法。
  • 调用父类构造方法时,super必须放在第一行。
  • 当有歧义的时候,通过super.可以明确表示调用父类的方法。
  • super同样可以引用父类非私有的变量。
  • 子类可以升级父类方法的可见性但不能降低。

super和this的区别

this引用一个对象,是实实在在存在的,可以作为函数参数,可以作为返回值,但super只是一个关键字,不能作为参数和返回值,它只是用于告诉编译器访问父类的相关变量和方法。

public class Base {
    private static final int MAX_NUM = 1000;
    private int[] arr = new int[MAX_NUM];
    private int count;
    public void add(int number){
        if(count < MAX_NUM){
            arr[count++] = number;
        }
    }

    public void addAll(int[] numbers){
        for(int num:numbers){
            add(num);
        }
    }
    
    public Base getBase(){
        return this;
    }
}

重载和重写

当有多个重名函数的时候,在决定调用哪个函数的过程中,首先是按照参数类型进行匹配的,换句话说,寻找在所有重载版本中最匹配的,然后才看变量的动态类型,进行动态绑定。

父子类型转换

public class Child extends Base {
    private long sum;

    @Override
    public void add(int number) {
        super.add(number);
        sum += number;
    }

    @Override
    public void addAll(int[] numbers){
        super.addAll(numbers);
        for(int i = 0;i<numbers.length;i++){
            sum += numbers[i];
        }
    }
    public long getSum(){
        return sum;
    }

    public static void main(String[] args) {
      
        Base b = new Child();
        Child c = (Child)b;
        
        Base d = new Base();
        // 编译通过,运行时报错:ClassCastException
        Child e = (Child)d;
    }
}

一个父类的变量能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型)是不是这个子类或这个子类的子类

类加载过程

在Java中,所谓类的加载是指将类的相关信息加载到内存。在Java中,类是动态加载的,当第一次使用这个类的时候才会加载,加载一个类时,会查看其父类是否已加载,如果没有,则会加载其父类。

寻找要执行的实例方法的时候,是从对象的实际类型信息开始查找的,找不到的时候,再查找父类类型信息。

虚方法表

虚方法表,就是在类加载的时候为每个类创建一个表,记录该类的对象所有动态绑定的方法(包括父类的方法)及其地址,但一个方法只有一条记录,子类重写了父类方法后只会保留子类的。

对变量的访问都是静态绑定的,无论是类变量还是实例变量。

chapter5 类的扩展

定义接口:
接口方法不需要加修饰符,加与不加相当于都是public abstract

与类不同,接口不能new,不能直接创建一个接口对象,对象只能通过类来创建。

针对接口而非具体类型进行编程,是计算机程序的一种重要思维方式。接口更重要的是降低了耦合,提高了灵活性。

接口可以使用instanceof关键字,用来判断一个对象是否实现了某接口。

接口中的变量修饰符是可选的,即使不写,也是public static final

使用接口替代继承

针对接口编程是一种重要的程序思维方式,这种方式不仅可以复用代码,还可以降低耦合,提高灵活性,是分解复杂问题的一种重要工具。

内部类的本质

内部类与包含它的外部类有比较密切的关系,而与其他类关系不大,定义在类内部,可以实现对外部完全隐藏,可以有更好的封装性,代码的实现上也往往更为简洁
内部类只是Java编译器的概念,对于Java虚拟机而言,它是不知道内部类这回事的,每个内部类最后都会被编译为一个独立的类,生成一个独立的字节码文件
内部类可以方便地访问外部类的私有变量,可以声明为private从而实现对外完全隐藏,相关代码写在一起,写法也更为简洁,这些都是内部类的好处。

根据定义的位置和方式不同,主要有四种内部类:

  • 静态内部类
  • 成员内部类
  • 方法内部类
  • 匿名内部类

方法内部类是在一个方法内定义和使用的;匿名内部类使用范围更小,它们都不能在外部使用;成员内部类和静态内部类可以被外部类使用,不过它们都可以被声明为private,这样外部就不能使用了。

1.静态内部类
public class Outer {
    private static int shared = 100;
    public static class StaticInner{
        public void innerMethod(){
            System.out.println("inner "+shared);
        }
    }

    // 在类内部,可以直接使用内部静态类
    public void test(){
        StaticInner si =  new StaticInner();
        si.innerMethod();
    }
}

静态内部类与外部类的联系也不大(与其他内部类相比)。它可以访问外部类的静态变量方法,如innerMethod直接访问shared变量,但不可以访问实例变量和方法。

public静态内部类可以被外部使用,只是需要通过“外部类.静态内部类”的方法使用,如下所示:

Outer.StaticInner si = new Outer.StaticInner();
si.innerMethod();

Outer类实际上会生成两个类:一个是Outer,另一个是Outer$StaticInner,代码如下所示:

public class Outer {
  private static int shared = 100;
  
  public void test() {
    Outer$StaticInner si = new Outer$StaticInner();
    si.innerMethod();
  }

  static int access$0(){
    return shared;
  }
}

public class Outer$StaticInner
{
  public void innerMethod() { System.out.println("inner " + Outer.access$0()); }
}

内部类访问了外部类的一个私有静态变量shared,而我们知道私有变量是不能被类外部访问的,Java的解决方法是:自动为Outer生成一个非私有访问方法access$0(),它返回这个私有静态变量shared。

静态内部类的使用场景是很多的,如果它与外部类关系密切,且不依赖于外部类实例,则可以考虑定义为静态内部类。

2.成员内部类

与静态内部类相比,成员内部类没有static修饰符。

public class Outer1 {
    private int a = 100;

    public class Inner {
        // error
        public static String name = "yyy";
        public void innerMethod() {
            System.out.println("outer a " + a);
            Outer1.this.action();
        }
    }

    private void action(){
        System.out.println("action");
    }

    public void test(){
        Inner inner = new Inner();
        inner.innerMethod();
    }
}

Inner就是成员内部类,与静态内部类不同,除了静态变量和方法,成员内部类还可以直接访问外部类的实例变量和方法。
与静态内部类不同,成员内部类对象总是与一个外部类对象相连的,在外部使用时,它不能直接通过new Outer.Inner()的方式创建对象,而是要先创建一个Outer对象:

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

与静态内部类不同,成员内部类中不可以定义静态变量方法(final变量例外,它等同于常量)。我们可以这样理解,这些内部类是与外部实例相连的,不应独立使用,而静态变量和方法作为类型的属性和方法,一般都是独立使用的,在内部类中意义不大,而如果内部类确实需要静态变量和方法,那么可以挪到外部类中。

成员内部类的背后实现:

public class Outer1 {
    private int a = 100;

    private void action() {
        System.out.println("action");
    }

    public void test() {
        Inner inner = new Inner();
        inner.innerMethod();
    }

    static int access$0(Outer1 outer){
        return outer.a;
    }

    static void access$1(Outer1 Outer){
        outer1.action();
}

public class Outer1$Inner{
    final Outer1 outer;
    public Outer1$Inner(Outer1 outer){
        this.outer = outer;
    }
    public void innerMethod(){
        System.out.println("outer1 a " + Outer1.access$0(outer));
        Outer1.access$1(outer);
    }
}

Outer1 I n n e r 类 有 个 实 例 变 量 o u t e r 指 向 外 部 类 的 对 象 , 它 在 构 造 方 法 中 被 初 始 化 , O u t e r 1 在 新 建 O u t e r 1 Inner类有个实例变量outer指向外部类的对象,它在构造方法中被初始化,Outer1在新建Outer1 InnerouterOuter1Outer1Inner对象时给它传递当前对象,由于内部类访问了外部类的私有变量和方法,外部类Outer1生成了两个非私有静态方法:access$0用于访问变量1,access$1用于访问方法action。

3.方法内部类
public class Outer2 {
    private int a = 100;

    public void test(final int param) {
        final String str = "hello";
        class Inner {
            public void innerMethod() {
                System.out.println("outer2 a " + a);
                System.out.println("param " + param);
                System.out.println("local var " + str);
            }
        }
        Inner inner = new Inner();
        inner.innerMethod();
    }
}

类Inner定义在外部类方法test中,方法内部类只能在定义的方法内被使用。如果方法是实例方法,则除了静态变量和方法,内部类还可以直接访问外部类的实例变量和方法,如innerMethod直接访问了外部私有实例变量a。如果方法是静态方法,则方法内部类只能访问外部类的静态变量和方法。方法内部类还可以直接访问方法的参数和方法中的局部变量
方法内部类示例的内部实现:

public class Outer2{
    private int a = 100;
    public void test(final int param){
        final String str = "hello";
        // 方法中的参数传递给了内部类
        Outer2Inner inner = new Outer2Inner(this,param);
        inner.innerMethod();
    }
    static int access$0(Outer2 outer){
         return outer.a;
    }
}
public class Outer2Inner{
    Outer2 outer;
    int param;
    Outer2Inner(Outer2 outer,int param){
         this.outer = outer;
         this.param = param;
    }
    public void innerMethod(){
        System.out.println("outer2 a " + Outer2.access$0(this.outer));
        System.out.println("param " + param);
        System.out.println("local a " + "hello");
    }

与成员内部类类似,Outer2Inner类也有一个实例变量outer指向外部对象,在构造方法中被初始化,对外部私有实例变量的访问也是通过Outer2添加的方法access$0来进行的。
方法内部类可以访问方法中的参数和局部变量,这是通过在构造方法中传递参数来实现的,如Outer2Inner构造方法中有参数int param,在新建Outer2Inner对象时,Outer2类将方法中的参数传递给了内部类。
在上面的代码中,String str并没有被作为参数传递,这是因为它被定义为了常量,在生成的代码中,可以直接使用它的值。
方法内部类操作的并不是外部的变量,而是它自己的实例变量,只是这些变量的值和外部一样,对这些变量赋值并不会改变外部的值,为避免混淆,所以干脆强制规定必须声明为final。

4.匿名内部类

匿名内部类没有单独的类定义,它在创建对象的同时定义类,语法如下:

new 父类(参数列表){
    // 匿名内部类实现部分
}

new 父接口(){
    // 匿名内部类实现部分
}

匿名内部类是与new关联的,在创建对象的时候定义类,new后面是父类或者父接口,然后是圆括号(),里面可以是传递给父类构造器方法的参数,最后是大括号{},里面是定义。

public class Outer3 {
    public void test(final int x, final int y) {
        Point p = new Point(2, 3) {
            @Override
            public double distance() {
                return Math.sqrt(x * x + y * y);
            }
        };
        System.out.println(p.distance());
    }

    public static void main(String[] args) {
        Outer3 o = new Outer3();
        o.test(3, 4);
    }
}

匿名内部类只能被使用一次,用来创建一个对象。它没有名字,没有构造方法,但可以根据参数列表,调用对应的父类构造方法。它可以定义实例变量和方法,可以有初始化代码块,初始化代码块可以起到构造方法的作用。因为没有构造方法,它自己无法接受参数,如果必须要有参数,则应该使用其他内部类。与方法内部类一样,匿名内部类也可以访问外部类的所有变量和方法,可以访问方法中的final参数和局部变量。

匿名内部类的内部实现:

public class Outer3{
    public void test(final int x, final int y){
         Point p = new Outer3$1(this,2,3,x,y);
         System.out.println(p.distance());
    }
}

public class Outer3$1 extends Point{
    int x2;
    int y2;
    Outer3 outer;
    Outer3$1(Outer3 outer,int x1,int y1,int x2,int y2){
        super(x1,y1);
        this.outer = outer;
        this.x2 = x2;
        this.y2 = y2;
    }

    @Override
    public double distance(){
        return Math.sqrt(x2 * x2 + y2 * y2);
    }
}

与方法内部类类似,外部实例this、方法参数x和y都作为参数传递给了内部类构造方法。此外,new时的参数2和3也传递给了构造方法,内部类构造方法又将它们传递给了父类构造方法。
匿名内部类能做的,方法内部类都能做。

枚举

枚举变量可以使用equals和==进行比较,结果是一样的。
在switch语句内部,枚举值不能带枚举类型前缀。

枚举的好处体现:

  • 定义枚举的语法更为简洁。
  • 枚举更为安全。
  • 枚举类型自带很多便利方法,易于使用。

chapter6 异常

Java的默认异常处理机制是退出程序,异常发生点后的代码都不会执行

throw关键字可以与return关键字进行比较:

  • return代表正常退出,throw代表异常退出;
  • return的返回位置是正确的,就是上一级调用者,而throw后执行哪行代码则经常是不确定的,由异常处理机制动态确定。

异常处理机制会从当前函数开始查看谁“捕获”了这个异常,当前函数没有就查看上一层,直到主函数,如果主函数也没有,就使用默认机制,即输出异常堆栈信息并退出

捕获异常后,程序就不会异常退出了,但try语句异常点之后的其他就不会执行了,执行完catch内的语句后,程序会继续执行catch花括号外的代码。

public class Exam2 {
    public void doSomething() {
        try {
            System.out.println("Hello");
            int a = 4 / 0;
            System.out.println("World");
            System.out.println("!");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Jack");
    }

    public static void main(String[] args) {
        Exam2 e = new Exam2();
        e.doSomething();
    }
}

异常是相对于return的一种退出机制,可以由系统触发,也可以由程序通过throw语句触发,异常可以通过try/catch语句进行捕获并处理,如果没有捕获,则会导致程序退出并输出异常栈信息。

受检异常(checked exception)和未受检异常(unchecked exception)。未受检异常也叫运行时异常。

受检和未受检的区别在于Java如何处理这两种异常。对于受检异常,Java会强制要求程序员进行处理,否则会有编译错误,对于未受检则没有这个要求。

finally

finally内的代码不管有无异常发生,都会执行:

  1. 如果没有异常发生,在try内的代码执行结束后执行。
  2. 如果有异常发生被catch捕获,在catch内的代码执行结束后执行。
  3. 如果有异常发生但没有被捕获,则在异常被抛给上层之前执行。

使用finally注意事项:

public class Exam {
    /**
     * finally中有return不仅会覆盖try和catch内的返回值,还会掩盖try和catch内的异常
     *
     * @return
     */
    public static int test() {
        int ret = 0;
        try {
            int a = 5 / 0;
            return ret;
        } finally {
            return 2;
        }
    }

    /**
     * finally中,如果finally中抛出了异常,则原异常也会被掩盖
     */
    public static void test2() {
        try {
            int a = 5 / 0;
        } finally {
            throw new RuntimeException("hello");
        }
    }

    public static void main(String[] args) {
        System.out.println(test());
        test2();
    }
}

输出结果:

2Exception in thread "main" 
java.lang.RuntimeException: hello
    at com.the.logic.of.java.programming.chapter6.Exam.test2(Exam.java:32)
    at com.the.logic.of.java.programming.chapter6.Exam.main(Exam.java:38)

chapter7 常用基础类

hashCode

hashCode()返回一个对象的哈希值。哈希值是一个int类型的数,由对象中一般不变的属性映射得来,用于快速对对象进行区分、分组等。一个对象的哈希值不能改变,相同对象的哈希值必须一样。

对两个对象,如果equals方法返回true,则hashCode也必须一样

hashCode的默认实现一般是将对象的内存地址转换为整数,子类如果重写了equals方法,也必须重写hashCode方法

创建包装类对象时,可以使用静态的valueOf方法,也可以直接使用new,建议使用valueOf方法。

通过共享常用对象,可以节省内存空间。

Arrays
  1. toString
  2. sort
  3. 查找
  4. 复制
  5. 比较
  6. 批量设置值
  7. 计算哈希

Apache开源包中的ArrayUtils中包含了更多的常用数组操作。

Calendar
Calendar calendar = Calendar.getInstance();
System.out.println("year:" + calendar.get(Calendar.YEAR));
System.out.println("month:" + calendar.get(Calendar.MONTH));
System.out.println("day:" + calendar.get(Calendar.DAY_OF_MONTH));
System.out.println("hour:" + calendar.get(Calendar.HOUR_OF_DAY));
System.out.println("minute:" + calendar.get(Calendar.MINUTE));
System.out.println("second:" + calendar.get(Calendar.SECOND));
System.out.println("millisecond:" + calendar.get(Calendar.MILLISECOND));
System.out.println("day_of_week:"+calendar.get(Calendar.DAY_OF_WEEK));
局限性

Date表示时刻,与年月日无关,Calendar表示日历,与时区和Locale相关,可进行各种运算,是日期时间操作的主要类,DateFormat/SimpleDateFormat在Date和字符串之间进行相互转换。
Date构造方法中的year表示的是与1900年的差,month是从0开始的。
DateFormat/SimpleDateFormat不是线程安全的。

随机
import java.util.Random;

public class Exam3 {
    public static void main(String[] args) {
        // 指定种子
        Random rnd = new Random(20160824);
        for (int i = 0; i < 5; i++) {
            System.out.print(rnd.nextInt(100) + " ");
        }
    }
}

种子决定了随机产生的序列,种子相同,产生的随机数序列就是相同的
指定种子是为了实现可重复的随机。

随机数基于一个种子,种子固定,随机数序列就固定,默认构造方法中,种子是一个真正的随机数

Random类是线程安全的,多个线程可以同时使用一个Random实例对象,不过并发性很高,会产生竞争,可以考虑使用多线程库中的ThreadLocalRandom类。

Java类库中的SecureRandom,可以产生安全性更高、随机性更强的随机数,用于安全加密等领域。

其他部分连接

第一部分 编程的基础与二进制

第三部分 泛型与容器
第四部分 文件
第五部分 并发
第六部分 动态与函数式编程
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值