Java基础回顾-01

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();
    }
}

输出:

 0
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,有两种类型:

  1. 类型Shape,称为shape的静态类型
  2. 类型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 {
    // 主体代码
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值