Head First Java读书笔记(二)

第8章:接口与抽象类(深入多态)
抽象类

用abstract关键字声明抽象类,抽象类不能用new 关键字进行实例化。在设计继承结构时,必须决定清楚什么类是抽象类,什么类是具体类。编译器不会让你初始化一个抽象类。抽象类,除了被继承以外,是没有其它任何用途的。抽象类中,必须包含有抽象方法,还可以包含非抽象方法。

抽象方法

即用 abstract关键字声明的方法,抽象方法没有方法实体,即没有具体的实现过程。拥有抽象方法的类,必须声明为抽象类。抽象类中的抽象方法,用于规定一组子类共同的协议。

abstract class Animal {
    // 抽象方法,没有方法体
    public abstract void eat();
}

在继承过程中,具体类必须实现抽象父类的所有抽象方法

抽象方法没有具体的方法体,它只是为了标记出多态而存在。在覆写抽象父类的抽象方法时,方法名、参数列表必须相同,返回值类型必须兼容。Java很在乎你是否实现了抽象类的抽象方法。

public class Canine extends Animal {
    // 覆写抽象类的抽象方法
    public void eat() {
        System.out.println("Canine,会吃食物!!");
    }
    // 非继承的方法
    public void roam() {
        
    }
}
多态的使用

在Java中,所有类都是从Object这个类继承而来的,Object是所有类的源头,它是所有类的父类。Object有很有用的方法,如 equals(), getClass(), hashCode(), toString()等。

Object类,是抽象类吗? 答:不是,它没有抽象方法。
是否可以覆写Object中的方法? 答:Object类中带有 final关键字的方法,不能被覆写。
Object类有什么用? 答:用途一,它作为多态可以让方法应付多种类型的机制,以及提供Java在执行期对任何对象都需要的方法实现。另一个用途,它提供了一部分用于线程的方法。
既然多态类型这么有用,为什么不把所有的参数类型、返回值类型都设定为Object? 答:因为Java是强类型语言,编译器会检查你调用的是否是该对象确实可以响应的方法。即,你只能从确实有该方法的类中去调用。
Object dog = new Dog();
dog.toString(); // 这可以通过编译,因为toString()是Object类中自有的方法。
dog.eat(); // 这将无法通过编译,因为dog是Object类型,它调用的eat()方法在Object类中没有。
在使用多态时,要注意对象多种类型之间的差异。如下代码:

Dog dog1 = new Dog();
Animal dog2 = new Dog();
Object dog3 = new Dog();

注意这三个dog对象的区别: dog1 拥有 Dog / Animal / Object中所有的方法。dog2 拥有 Animal / Object 中的方法,不能调用 Dog 类特有的方法。 dog3 只拥有Object 中的方法,不能调用 Animal / Dog类中的方法。这就是在使用多态过程中,需要特别注意的问题。

那么该如何把 Object 类型的 dog转化成真正的 Dog 类型呢?

if (dog2 instanceof Dog) {
  Dog dog4 = (Dog)dog2;
}
if (dog3 instanceof Dog) {
  Dog dog5 = (Dog)dog3;
}

// 此时,dog4 / dog5 就是真正的 Dog类型了。

接口

接口,是一种100%纯抽象的类。接口中的所有方法,都是未实现的抽象方法。

接口的作用

接口存在的意义,就是为了解决Java多重继承带来的致命方块问题。为什么接口可以解决致命方块的问题呢?因为在接口中,所有方法都是抽象的,如此一来,子类在实现接口时就必须实现这些抽象方法,因此Java虚拟机在执行期间就不会搞不清楚要用哪一个继承版本了。

// interface关键字,用于定义接口
public interface Pet {
    public abstract void beFriendly();
    public abstract void play();
}
// 继承抽象父类 Animal类, 实现 Pet接口
public class Dog extends Animal implements Pet {
    // 实现接口中的抽象方法
    public void beFriendly() {
        System.out.println("实现 Pet接口中的 beFriendly()方法");
    }
    // 实现接口中的抽象方法
    public void play() {
        System.out.println("实现 Pet接口中的 play()方法");
    }
    // 覆写抽象父类中的抽象方法
    public void eat() {
        System.out.println("覆写抽象父类中的eat()抽象方法");
    }
}

同一个类,可以实现多个接口!

public class Dog extends Animal implements Pet, Saveable, Paintable { ... }
super关键字

super代表父类,在子类中使用 super关键字指代父类,通过super还可以调用父类的方法。

// 抽象父类
abstract class Animal {
  void run () {}
}
// 继承父类
class Dog extends Animal {
  void run () {
    super.run();  // 这里,调用并执行父类的 run() 方法
    // do other things
  }
}
Dog d = new Dog();
d.run();    // 这调用的是子类Dog对象的 run()方法。

第9章:构造器与垃圾收集器
堆(heap)、栈(stack)

当Java虚拟机启动时,它会从底层操作系统中取得一块内存,以此区段来执行Java程序。实例变量保存在所属的对象中,位于堆上。如果实例变量是对象引用,则这个引用和对象都是在堆上。

构造函数与对象创建的三个步骤

对象创建的三个步骤:声明、创建、赋值。
构造函数,让你有机会介入 new 的过程。构造函数,没有显示的指定返回值类型,构造函数不会被继承。如果一个类,没有显示地编写构造器函数,Java编译器会默认地为该类添加一个没有参数的构造器函数。反之,Java编译器则不会再添加任何默认的构造函数。

Dog dog = new Dog();

构造器函数重载

即一个类,有多个构造器函数,且它们的参数都不能相同,包括参数顺序不同、或者参数类型不同、或者参数个数不同。重载的构造器,代表了该类在创建对象时可以有多种不同的方式。

public class Mushroom {
// 以下五个构造器,都是合法的,即构造器重载
public Mushroom() {}
public Mushroom( int size ) {}
public Mushroom( boolean isMagic ) {}
public Mushroom( boolean isMagic, int size ) {}
public Mushroom( int size, boolean isMagic ) {}
}

构造函数链 super()

构造函数在执行的时候,第一件事就是去执行它的父类的构造函数。这样的链式过程,就被称为“构造函数链(Constructor Chaining)”。

class Animal {
    public Animal() {
        System.out.println("Making an Animal");
    }
}

class Dog extends Animal {
    public Dog() {
        super();    // 如果没有这句,Java编译器会默认添加上这句,即调用父类的无参构造器
        System.out.println("Making an Dog");
    }
}

public class ChainTest {
    public static void main(String[] args) {
        System.out.println("Starting...");
        Dog d = new Dog();
    }
}

如果一个类,没有显示地书写构造器函数,Java编译器会为它添加上默认的无参构造器。如果在一个子类的构造器中没有使用super()调用父类的某个重载构造构造器,Java编译器会为这个子类的构造器默认添加上super(),即在子类的构造器函数中调用父类的无参构造器。
父类的构造器函数,必须在子类的构造器函数之前调用。在子类构造器函数中调用父类构造器时,必须与父类构造器的参数列表一致。

在类中,this 和 super 有什么区别? this() 和 super() 有什么区别?

使用 this() 可以在某个构造函数中调用同一个类的另外一个构造函数。 this() 只能在构造函数中使用,并且必须是第一行。 this() 和 super() 不能同时使用。

class Car {
    private String name;
    // 父类的有参构造器
    public Car(String name) {
        this.name = name;
        System.out.println(name);
    }
}

class MiniCar extends Car {
    // 构造器
    public MiniCar(String name) {
        // 调用父类的有参构造器
        super(name);
    }
    // 另一个构造器
    public MiniCar() {
        // 调用同一个类的另一个构造器
        this("这里子类汽车的名称");
    }
}

public class TestThis {
    public static void main(String[] args) {
        MiniCar mc1 = new MiniCar();
        MiniCar mc2 = new MiniCar("动态的名字");
    }
}
对象、变量的生命周期

对象的生命周期决定于对象引用变量的生命周期,如果引用还在,则对象也在;如果引用死了,对象会跟着被 GC 回收。当最后一个引用消失时,对象就会变成可回收的。
局部变量,只存活在对象的方法中,方法结束,局部变量就死了。
实例变量,存活在对象中,它的生命周期与对象一致。

Life 和 Scope的区别

Life,只要变量的推栈块还存在于堆栈上,局部变量就算是活着。局部变量会活到方法执行完毕为止。
Scope,局部变量的作用范围只限于它所在的方法中。当该方法调用别的方法时,该局部变量还活着,但不在目前的范围内,当被调用的其它方法执行完毕后,该总局变量的范围又跟着回来了。


第10章:数字与静态性
Math的特点

在 Java 中没有东西是全局(global)的。但,Math 方法是接近全局的方法。Math不能用来创建实例变量。因为Math是用来执行数据计算的,所以没有必要创建对象来进行数学计算,创建对象是浪费内存空间的做法。Math中所有方法都静态方法。
···java
Long a = Math.round(46.25);
int b = Math.max(2, 3);
int c = Math.abs(-500);
···

非静态方法与静态方法

静态方法,使用 static 关键字声明,以类的名称进行调用。
非静态方法,以实例对象进行调用,没有 static修饰。
Math类是如何阻止被实例化的?

Math类阻止被实例化,采取的策略是使用 private 修饰了其构造函数。

静态变量,会被同类的所有实例共享,因为它隶属于类。静态变量,在类第一次载入时初始化。静态变量,会在所有对象创建之前进行初始化,也会在任何静态方法执行之前就初始化。静态变量,只能由类来调用。
非静态变量,只被单个对象独有,它隶属于实例。非静态变量,在类实例化时进行初始化。
实例对象不会维护静态变量的拷贝,静态变量由类进行维护。非静态变量由实例对象进行维护。

class Duck {
    // 非静态变量,属于对象
    private int size = 0;
    // 静态变量,属于类
    private static int count = 0;
    
    public Duck() {
        size++;
        count++;
        System.out.println("size " + size);
        System.out.println("count " + count);
    }
    public void setSize(int s) {
        size = s;
    }
    public int getSize() {
        return size;
    }
}

public class TestStatic {
    public static void main(String[] args) {
        Duck d1 = new Duck();  // size = 1     count = 1
        Duck d2 = new Duck();  // size = 1     count = 2
        Duck d3 = new Duck();  // size = 1     count = 3
    }
}
声明一个静态常量

public static final double PI = 3.1415926;
public 表示可供各方读取。 static 表示静态。 final 表示“固定不变”。 常量的标识符,一般建议字母大写,字母之间可以用下划线连接。

深入理解 final

final 的核心意思是,它所修饰的元素不能被改变。final 不仅可以修饰变量,还可以修饰方法和类。

// 用 final 修饰的类,不能被继承
final class Foo {
    // final 修饰静态变量,得到静态常量
    public static final double PI = 3.1415926;
    // final 修饰非静态变量,该变量将无法再被修改
    final String name = "geekxia";
    void changeName() {
        // 修改 final变量,失败
        // name = "Evatsai";
    }
    // final 修饰局部变量,该局部变量也将无法再被修改
    void doFoo(final int x) {
        // 修改局部变量,失败
        // x = 100;
    }
    // final 修饰的方法,子类将不能覆写
    final String getName() {
        return name;
    }
}
主数据类型的包装类

Boolean / Character / Byte / Short / Integer / Long / Float / Double
主数据类型的包装类,都放在 java.lang 中,所以无需 import 它们。当你需要以对象的方式来处理主数据类型时,你就需要用包装类把它们包装起来,Java5.0之前必须这么做。

int a = 123;
// 包装
Integer aWrap = new Integer(a);
// 解包
int b = aWrap.intValue();

Java5.0以后,autoboxing 使得主数据类型和包装类型不必分得那么清楚。autoboxing 的功能能够自动地将主数据类型和包装类型进行转换。看看下面的例子:

ArrayList<Integer> list = new ArrayList<Integer>();
// 自动地把主数据类型和包装类型进行转换
list.add(3);
int c = list.get(0);
        
Boolean bool = new Boolean(null);
if (bool) {}
        
Integer d = new Integer(3);
d++;
Integer e = d + 3;

void takeNumber(Integer i) {}
Integer getNumber() { return 4; }
主数据类型与字符串之间的相互转化
// 把字符串转化成主数据类型
String a = "12";
int b = Integer.parseInt(a);        
double c = Double.parseDouble("50.789");        
boolean d = new Boolean("true").booleanValue();
        
// 把主数据类型转化成字符串
double e = 34.789;
String f = "" + e;
String g = Double.toString(e);
如何对数字进行格式化?

// 第二个参数,以第一个参数的格式化指令进行输出
String s = String.format("%, d", 1000000000);
String m = String.format("I have %,.2f bugs to fix.", 489369.123456);
如何对日期进行格式化?

String d = String.format("%tB", new Date());
更多有关“格式化指令”的参考,请查看相关手册。

使用 Calendar 抽象类操作日期

// 获取日历的实例对象
Calendar cal = Calendar.getInstance();
cal.set(2014, 1, 7, 15, 40);
        
long day1 = cal.getTimeInMillis();
day1 += 1000*60*60;
        
cal.setTimeInMillis(day1);
cal.add(cal.DATE, 35);

第11章:异常处理

如果你把有风险的程序代码包含在 try/catch 块中,那么编译器会放心很多。 try/catch 块会告诉编译器你确实知道所调用的方法会有风险,并且也已经准备好要处理它。

try {
    // 有风险的行为
    Sequencer seq = MidiSystem.getSequencer();
    System.out.println("had got a sequencer");
} catch (MidiUnavailableException ex) {
    System.out.println(ex);
} finally {
    System.out.println("总会被执行!");
}

异常也是多态的,Exception子类的实例对象,都是 Exception类型的。编译器会忽略 RuntimeException类型的异常,RuntimeException类型的异常不需要声明或被包含在 try/catch 块中。

如果 try 或 catch 块中有 return 指令,finally还是会执行。流程会跳到 finally执行,finally执行完毕后再回跳到return 指令。

处理多重异常
class Laundry {
    public void doLaundry() throws PantsException, LingerieException {}
}

public class HandleException {
    public void go() {
        Laundry lau = new Laundry();
        try {
            lau.doLaundry();
        } catch (PantsException e) {
            System.out.print(e);
        } catch (LingerieException e) {
            System.out.print(e);
        } finally {
        }
    }
}

如果有必要的话,方法可以抛出多个异常。但该方法的声明必须要有含有全部可能的检查异常(如果两个或两个以上的异常有共同的父类时,可以只声明该父类就行)。

有多重异常需要捕获时,异常要根据继承关系从小到大排列,如果更高一级的异常排在了前面,将会导致低一级的异常将没有机会被使用。同级别的异常,排列顺序无所谓。

异常

异常是由方法 throws 来的。

public void doLaundry() throws PantsException, LingerieException {}
如果你调用了一个有风险的方法,但你又不想 try/catch捕获时怎么办?你可以继续使用 throws 关键字,把异常抛给下一个调用我的人,这好比是在踢皮球哈哈。

从上面看,程序有两种方式来处理异常,一种是使用 try/catch 来捕获异常,另一种是使用 throws 把异常抛给下一个调用者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值