1 基础语法
Java不仅是一种语言,Java是一个完整的平台,有一个庞大的库,其中包含了很多可重用的代码和一个提供诸如安全性、跨操作系统的可移植性以及自动垃圾收集等服务的执行环境。
面向对象:是一种程序设计技术。它将重点放在数据(即对象)和对象的接口上。
1.1 封装
在面向对象那个设计方法中,封装(Encapsulation)是一种将抽象函数式接口的实现细节包装、隐藏起来的方法。
封装就是隐藏实现细节,提供简化接口。使用者只需要关注怎么用,而不需要关注内部是怎么实现的。实现细节可以随时修改,而不影响使用者。函数是封装,类也是封装。通过封装,才能在更高的层次上考虑和解决问题。
可以说,封装是程序设计的第一原则,没有封装,代码之间会存在着实现细节的依赖,则构建和维护复杂的程序是难以想象的。
- 可以被认为是一种保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
- 要访问该列的代码和数据,必须通过严格的接口控制。
- 封装最主要的功能在于我们修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
- 适当的封装可以让程式更容易理解与维护,也加强了程序的安全性。
典型示例:
私有化成员变量,公有化访问方法。
public class Encaptest {
private String name;
private String idNum;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdNum() {
return idNum;
}
public void setIdNum(String idNum) {
this.idNum = idNum;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
对类进行测试:
public class RunEncap {
public static void main(String[] args) {
Encaptest encap = new Encaptest();
encap.setName("wonjie");
encap.setIdNum("008008008");
encap.setAge(18);
System.out.println("Name : " + encap.getName() + "\n" +"Age : " + encap.getAge());
}
}
输出结果:
Name : wonjie
Age : 18
1.2 继承
就是子类继承父类的特征和行为,使得子类对象(实例) 具有父类的实例域和方法,或者子类从父类继承方法,使得子类具有父类相同的行为。
与封装的关系:由于子类与父类之间可能存在着实现细节的依赖,继承可能会破坏封装。
class Parent {
public void run() {
System.out.println("I just run.");
}
}
public class Son extends Parent {
public static void main(String[] args) {
Son son = new Son();
son.run();
}
}
输出:
I just run.
可以看出,Son类并没有定义方法和属性,但是通过继承Parent类,就可以具有run()方法。
注:Java支持支持单继承,不支持多继承,但支持多重复继承。

特性:
- 子类可以拥有父类的非private的属性、方法
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方法实现父类的方法
super 和 this 的区别:
- this引用一个对象,是实实在在的,可以作为函数参数,可以作为返回值
- super是一个关键字,不能作为参数和返回值,它只是用于告诉编译器访问父类的相关变量和方法
使用继承的一个好处是可以统一处理不同子类型的对象。
关于继承需要注意的点:
- 如果父类没有默认的构造方法,它的任何子类都必须在构造方法中通过super调用Base的带参数构造方法。否则Java会提示编译错误。
class Base {
private String member;
public Base(String member) {
this.member = member;
}
}
public class Child extends Base {
private int a = 123;
public Child(String member) {
super(member);
}
}
- 如果在父类构造方法中调用了可被重写的方法,则可能出现意想不到的结果。
class Base {
public Base() {
test();
}
public void test() {
}
}
public class Child extends Base {
private int a = 123;
public Child() {
}
public void test() {
System.out.println(a);
}
}
测试:
public class Test {
public static void main(String[] args) {
Child c = new Child();
c.test();
}
}
输出:
123
第一次输出是在new过程中输出的,在new过程中,首先初始化父类,父类构造函数调用test()方法,test()方法被子类重写了,就会调用子类的test()方法,子类方法访问子类实例变量a,而这个时候子类的实例变量的赋值语句和构造方法还有没执行,所以输出的是其默认值0。
1.2.1 重名和静态绑定
基类代码:
public class Base {
public static String s = "static_base";
public String m = "base";
public static void staticTest() {
System.out.println("base static:" + s);
}
}
子类代码:
public class Child extends Base {
public static String s = "child_base";
public String m = "child";
public static void staticTest() {
System.out.println("child static:" + s);
}
}
子类定义了和父类重名的变量和方法。对于一个子类对象,它就有了两份变量和方法,在子类内部访问的时候,访问的是子类的,或者说,子类变量和方法隐藏了父类对应的变量和方法。
测试:
创建了一个子类对象,然后将对象分别赋值给子类引用变量c和父类引用变量b,然后通过b和c分别引用变量和方法。
public class Test {
public static void main(String[] args) {
Child c = new Child();
Base b = c;
System.out.println(b.s);
System.out.println(b.m);
b.staticTest();
System.out.println(c.s);
System.out.println(c.m);
c.staticTest();
}
}
static_base
base
base static:static_base
child_base
child
child static:child_base
通过b访问的是Base的变量和方法,当通过c访问时,访问的是Child的变量和方法,这称之为静态绑定,即访问绑定到变量的静态类型。静态绑定是程序编译阶段即可决定,而动态绑定则到等到程序运行时。
实例变量、静态变量、静态方法、private方法,都是静态绑定的。
1.2.2 重载和重写
重载:方法名称相同但参数签名不同(参数个数、类型或顺序不同)
重写:子类重写与父类相同参数签名的方法
基类代码:
public class Base {
public int sum(int a, int b) {
System.out.println("base_int_int");
return a+b;
}
}
子类代码:
public class Child extends Base {
public long sum(long a, long b) {
System.out.println("child_long_long");
return a+b;
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Child c = new Child();
int a = 2;
int b = 3;
c.sum(a,b);
}
}
Child和Base都定义了sum方法,子类的sum方法虽然不完全匹配但是是兼容的,父类的sum方法参数类型是完全匹配的。输出:
base_int_int
如果将父类改为:
public class Base {
public long sum(int a, long b) {
System.out.println("base_int_long");
return a+b;
}
}
输出:
base_int_long
调用依然是父类的方法,父类和子类的两个方法的类型都不完全匹配,由于父类的更匹配一点,因此仍然调用父类方法。再修改子类代码:
public class Child extends Base {
public long sum(int a, long b) {
System.out.println("child_int_long");
return a+b;
}
}
目前子类与父类方法相同,同样不匹配,此时,输出:
child_int_long
因此可以看出:当多个重名函数的时候,在决定要调用那个函数的过程中,首先是按照参数类型进行匹配,换句话说,寻找在所有重载版本中最匹配的,然后才看变量的动态类型,进行动态绑定。
PS:当子类和父类方法都是完全匹配时,调用子类方法。
1.2.3 父子类型转换
向上转型:子类型的对象可以赋值给父类型的引用变量
Base b = new Child();
向下转型:父类型的对象赋值给子类型的引用变量(不一定转换成功)
Child c = (Child)b;
Child c = (Child)b;就是将变量b的类型强制转换为Child并赋值为c,这是没有问题的,因为b的动态类型就是Child,但是下面的转化方式就不可以。
Base b = new Base();
Child c = (Child)b;
语法上不会报错,但运行时会抛出错误,错误为类型转换异常。
Exception in thread "main" java.lang.ClassCastException: beforeJob.Base cannot be cast to beforeJob.Child
一个父类变量能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型) 是不是这个子类或这个子类的子类。
1.2.4 继承访问权限protected
public:外部可以访问
private:只能内部访问
protected:可以被子类访问,还可以被同一包中的其他类访问,不管其他类是不是该类的子类。
基类代码:
public class Base {
protected int currentStep;
protected void step1() {
}
protected void step2() {
}
public void action() {
this.currentStep = 1;
step1();
this.currentStep = 2;
step2();
}
}
action表示对外提供的行为, 内部有两个步骤step1()和step2(),使用currentStep变量表示当前进行到了那个步骤,step1()、step2()和currentStep是protected的,子类一般不重写action,而只是重写step1和step2,同时,子类可以直接访问currentStep查看进行到哪一步。子类代码:
public class Child extends Base {
public void step1() {
System.out.println("child step " + this.currentStep);
}
protected void step2() {
System.out.println("child step " + this.currentStep);
}
}
测试:
public class Test {
public static void main(String[] args) {
Child c = new Child();
c.action();
}
}
输出:
child step 1
child step 2
基类定义了表示对外行为的方法action,并定义了可以被子类重写的两个步骤step1()和step2() ,以及被子类查看的变量currentStep,子类通过重写protected方法step1()和step2()来修改对外的行为。
这种思路和方法是一种设计模式,称之为模板方法。action方法就是一个模板方法,它定义了实现的模板,而具体实现则由子类提供。模板方法在很多框架中有广泛的应用,这是使用protected的一种常见场景。
1.2.5 其他
可见性重写:重写时,子类方法不能降低父类方法的可见性。
final修饰类-防止继承;修饰方法-防止重写。
1.2.6 继承实现的基本原理
基类:
public class Base {
public static int s;
private int a;
static {
System.out.println("基类静态代码块,s:" + s);
s = 1;
}
{
System.out.println("基类实例代码块,a:" + a);
a = 1;
}
public Base() {
System.out.println("基类构造方法,a:" + a);
a = 2;
}
protected void step() {
System.out.println("base s : " + s + ", a : " + a);
}
public void action() {
System.out.println("start");
step();
System.out.println("end");
}
}
子类:
public class Child extends Base {
public static int s;
private int a;
static {
System.out.println("子类静态代码块,s:" + s);
s = 10;
}
{
System.out.println("子类实例代码块,a:" + a);
a = 10;
}
public Child() {
System.out.println("子类构造方法,a:" + a);
a = 20;
}
protected void step() {
System.out.println("child s : " + s + ", a : " + a);
}
}
测试:
public class Test {
public static void main(String[] args) {
System.out.println("---new Child()");
Child c = new Child();
System.out.println("\n---c.action()");
c.action();
Base b = c;
System.out.println("\n---b.action()");
b.action();
System.out.println("\n---b.s:" + b.s);
System.out.println("\n---c.s:" + c.s);
}
}
输出:
---new Child()
基类静态代码块,s:0
子类静态代码块,s:0
基类实例代码块,a:0
基类构造方法,a:1
子类实例代码块,a:0
子类构造方法,a:10
---c.action()
start
child s : 10, a : 20
end
---b.action()
start
child s : 10, a : 20
end
---b.s:1
---c.s:10
1.3 多态
一种类型的变量,可以引用多种实际类型对象。
例子:
public class ShapeManager {
private static final int MAX_NUM = 100;
private Shape[] shapes = new Shape[MAX_NUM];
private int shapeNum = 0;
public void addShape(Shape shape) {
if (shapeNum < MAX_NUM) {
shapes[shapeNum++] = shape;
}
}
public void draw() {
for (int i = 0; i < shapeNum; i++) {
shapes[i].draw();
}
}
}
public class ManageTest {
public static void main(String[] args) {
ShapeManager manager = new ShapeManager();
manager.addShape(new Circle(new Point(4,4),3));
manager.addShape(new Line(new Point(2,3),new Point(3,4),"green"));
manager.addShape(new ArrowLine(new Point(1,2),new Point(5,5),"black",false,true));
manager.draw();
}
}
对于变量shape,有两种类型:
- 类型Shape,称为shape的静态类型
- 类型Circle/Line/ArrorLine,称之为shape的动态类型。在ShapeManager的draw方法中,shape[i].draw()调用的是其对应动态类型的draw方法,这种称之为方法的动态绑定。
为什么要有多态和动态绑定?
创建对象的代码和操作对象的代码,经常不在一起,操作对象的代码往往只知道对象是某种父类型,也往往只需要知道它是某种父类型就可以了。
多态和动态绑定是计算机程序中的一种重要思维方式,使得操作对象的程序不需要关注对象的实际类型,从而可以统一处理不同对象,但又能实现每个对象的特有行为。
子类对象可以赋值给父类引用变量,这叫多态;实际执行调用的是子类实现,这叫动态绑定。
2 容器
3 异常
3.1 异常分类
Throwable是所有异常的基类,它有两个子类:Error和Exception。
Error表示系统错误或资源耗尽,由Java系统自己使用,应用程序不抛出和处理。如上图虚拟机错误(VirtualMachineError)及其子类内存溢出错误和栈溢出错误。
Exception表示应用程序错误,它有很多子类,应用程序也可以通过继承Exception或其子类创建自定义异常。
如此多不同的异常类其实并没有比Throwable这个基类多多少属性和方法,大部分类在继承父类之后只是定义了几个构造方法,这些构造方法也只是调用了父类的构造方法,并没有额外的操作。
定义这么多不同的类,是因为名字不同。异常类名字本身就代表异常的关键信息,无论是抛出还是捕获异常,使用合适的名字都有助于代码的可读性和可维护性。
自定义异常:
public class AppException extends Exception {
public AppException() {
super();
}
public AppException(String message, Throwable cause) {
super(message, cause);
}
public AppException(String message) {
super(message);
}
public AppException(Throwable cause) {
super(cause);
}
}
3.2 异常处理
3.2.1 catch匹配
try {
// 可能触发异常的代码
} catch (NumberFormatException e) {
System.out.println("not valid number");
} catch (RuntimeException e) {
System.out.println("runtime exception " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
3.2.2 重新抛出异常
对catch块内处理完后,可以重新抛出异常。这个异常可以是原来的,也可以是新建的。
try {
// 可能触发异常的代码
} catch (NumberFormatException e) {
System.out.println("not valid number");
throw new AppException("输入格式不正确", e);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
3.2.3 finally
catch后面可以跟finally语句,语法如下:
try {
// 可能抛出异常
} catch () {
// 捕获异常
} finally {
// 不管有无异常都执行
}
3.2.4 try-with-resources
对于一些使用资源的场景,比如文件和数据库连接,典型的使用流程是首先打开资源,最后在finally语句中调用资源的关闭方法。针对这种场景,Java7开始支持一种新的语法,称为try-with-resources,这种语法针对实现了java.lang.AutoCloseable接口的对象,该对象定义为:
public interface AutoCloseable {
void close() throws Exception;
}
没有try-with-resources时,使用形式如下:
public static void useResource() throws Exception {
AutoCloseable r = new FileInputStream("hello"); //创建资源
try {
// 使用资源
} finally {
r.close();
}
}
使用try-with-resources语法,形式如下:
public static void useResource() throws Exception {
try(AutoCloseable r = new FileInputStream("hello")) { //创建资源
// 使用资源
}
}
3.2.5 throws
异常机制中,还要一个和throw很像的关键字throws,用于声明一个方法可能抛出的异常,语法如下所示:
public void test() throws AppException, SQLException, NumberFormatException {
// 主体代码
}