JAVA basic - zhongjiajie/zhongjiajie.github.com GitHub Wiki

Java-basic

Java基础特征

程序的一些基本特征

  • Java中的主要代码都会位于一个类中,类是通过class关键字来定义的
  • Java是一种大小写敏感的语言,例如:
    • 关键字区分大小写,class不能写成Class
    • 命名为HelloWorld的类和命名为Helloworld的类是不一样的
  • 程序的文件名称必须和类的名称完全相同,Java代码的文件都以类名加.java后缀进行命名
    • 比如HelloWorld类的代码保存在HelloWorld.java文件中
  • public static void main(String[] args)是一个方法,这是Java程序的入口
    • 任何Java程序的代码都是从这个方法开始执行的
  • 关于Java标识符
    • 所有的标识符都应该以字母(A-Z或者a-z),美元符($)、或者下划线(_)开始
    • 首字符之后可以是字母(A-Z或者a-z),美元符($)、下划线(_)和数字的组合
    • Java语言自带的关键字不能用作标识符,比如你不能定义一个类或者方法命名为class
    • 标识符是大小写敏感的
    • 比如blog、$user、_title和__1_content都是合法的标识符;而123blog和-user都是非法标识符
  • 代码注释
    • 单行注释: 在注释内容前加两个斜线//,则Java编译器会忽略掉//的信息
    • 多行注释: 在要注释的内容前面添加/*,在注释的内容后添加*/
    • 文档注释: 在要注释的内容前面添加/**,在注释的内容后添加**/,这是一种特殊的多行注释,注释中的内容可以用以生成程序的文档,具体用法我们以后讲解.
  • JAVA中的包: 在开发过程中,类的数量会越来越多,我们可以通过包(Package)来组织类.包的命名一般以一个组织的域名的反写开头.如com.apache

变量

变量(Variable)是在内存中动态存储值的地方.简单地理解,程序的作用就是对变量进行各种运算. java中的类型分为基本数据类型以及引用数据类型.声明变量的时候要确定变量的数据类型,使用变量之前要对变量进行初始化.

  • 对大数的记录

结尾的"E+数字"表示E之前的数字要乘以10的“数字”次幂.比如3.14E3就是3.14×1000=3140

一个类可以包含以下类型的变量

  • 局部变量: 在方法、构造方法或者语句块中定义的变量被称为局部变量.变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁.
  • 成员变量: 成员变量是定义在类中,方法体之外的变量.这种变量在创建对象的时候实例化.成员变量可以被类中方法、构造方法和特定类的语句块访问.
  • 类变量: 类变量也声明在类中,方法体之外,但必须声明为static类型.

转义字符

符号 字符含义
\n 换行 (0x0a)
\r 回车 (0x0d)
\f 换页符(0x0c)
\b 退格 (0x08)
\s 空格 (0x20)
\t 制表符
\" 双引号
\' 单引号
\ 反斜杠
\ddd 八进制字符 (ddd)
\uxxxx 16进制Unicode字符 (xxxx)

运算符

// 对a减1
a++;
a += 1;
a = a + 1;
// 对a加1
a--;
a -= 1;
a = a - 1;
  • 三目运算符:表达式1?表达式2:表达式3;当表达式1为真的时候执行表达式2,否则执行表达式3

  • 相除向下取整:/将商向下取整int a = 11, b = 3; int c = a / b;

  • 取余运算:%获得两个数相除的余数

  • 比较关系运算符是否相等

    • '=='和'!='作为关系运算符只用来比较对象的引用
    • 如果想比较两个对象实际内容是否相同,需要调用对象的equals()方法.比如判断一个字符串str的内容是否为"abcd",应该这样比较
    if (str.equals("abcd")) {
    }
    if (str == "abcd") {
    }
  • 逻辑运算符

逻辑运算符 逻辑关系
&&
||
  • 逻辑短路

java支持逻辑短路&&前面如果为false整个值都是false,||的前面如果为true整个表达式的值都是true

  • 位运算符
位运算符 计算逻辑
& 与运算: 对于某一位,只要两个操作数都为1,则该位的结果为1,否则为0
| 或运算: 对于某一位,只要两个操作数有一个为1,则该位的结果为1,否则为0
^ 异或运算: 对于某一位,如果两个操作数不相同,则该位的结果为1,否则为0
~ 非运算: 按位补运算符翻转操作数的每一位
<< 二进制左移运算符: 左操作数按位左移右操作数指定的位数
>> 二进制右移运算符: 左操作数按位右移右操作数指定的位数
>>> 二进制右移补零操作符: 左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充
x = 0011 1100
y = 0000 1101
-----------------
x & y = 0000 1100
x | y = 0011 1101
x ^ y = 0011 0001
~x  = 1100 0011
x << 2 = 1111 0000
x >> 2 = 0000 1111
x >>> 2 = 0000 1111
  • 三元运算符
booleanExpression ? valueWhenTrue : valueWhenFalse

if (x >= 0) {
    y = x;
} else {
    y = -x;
}
// 可以转换成
y = x >= 0 ? x : -x;

打印数组元素

  • Arrays.toString(array);: 直接使用system.out.print(array)会出现类似[I@610455d6这样的情况

程序的控制流

java中存在着三种控制结构: 顺序结构 循环结构 选择结构

顺序结构

按照顺序从前到后运行代码

循环结构

根据一定条件循环代码,只有当满足跳出条件或者循环结束时才会结束控制结构

  • while循环: 要满足条件才能才能进入循环
  • do/while循环: 先执行了一遍然后再判断是否满足条件进行循环,不管能否满足都会先执行一遍
  • for循环: 一直遍历直到遍历完才跳出
    • Java利用for语句引入了更为简单的方式来进行Java数据及容器的操作. 通过这种方式,我们可以不用引入额外的控制变量.以遍历数组为例: 一般的for循环:

      // 比较原始的方法
      String[] sentences = {"hello", "thank u", "thank u very much"};
      for (int index = 0; index < sentences.length; index++) {
          System.out.println(sentences[index]);
      }
      // 不引入额外变量的方式
      String[] sentences = {"hello", "thank u", "thank u very much"};
      for (String sentence : sentences) {
          System.out.println(sentence);
      }

选择结构

if结构

if (布尔表达式) {
    //布尔表达式的值为true将执行的代码
}

if (布尔表达式) {
    //布尔表达式的值为true将执行的代码
} else {
    //布尔表达式的值为false将执行的代码
}

if (布尔表达式1) {
    //布尔表达式1的值为true将执行的代码
} else if (布尔表达式2) {
    //布尔表达式1为false,布尔表达式2为true将执行的代码
} else if (布尔表达式3) {
    //布尔表达式1为false,布尔表达式2为false,布尔表达式3的值为true将执行的代码
} else {
    //以上布尔表达式都不为true将执行的代码
}

switch结构

  • switch表达式中变量只能为Stringbyteshortint或者char类型.
  • switch可以有多个case语句,后面跟一个case <values> :.
  • case语句中的值的数据类型必须与变量的数据类型相同,而且只能是常量.
  • 当表达式的值和case语句的值相等时,就执行对应case中的语句,直到break语句出现才会跳出switch语句.
  • 每个case语句不一定要包含break语句.如果没有break,程序会执行下一个case条件,直到出现break出现.so-switch-without-break
  • switch可以有default分支,但必须是最后一个分支.只有当全部的case值都不能匹配上时才进入defautl分支.default分支不需要break语句.
switch(expression){
    case value :
        //语句
    break; //可选
    case value :
        //语句
    //可以有任意数量的case语句
    default : // 可选
        //语句
}

控制流的其他组成部分

  • break: 跳出全部循环
  • continue: 终止当前循环,进入下一个循环
  • return: 无条件分支,如果后面跟了变量或值就是该方法的返回值.如果仅仅有return;没有具体的返回值,表示退出当前方法

方法

  • 如果函数返回值是一个新建对象的时候,要用new关键字声明一个对象return new int[] {1,2,3};

  • 可以通过overload的方式限制参数的个数,从而实现默认值

    public void test() {
        test("exampleText");
    }
    public void test(String name) {
        // what actully do here
    }

override

  • @Override的含义
    • 是Java5的元数据,自动加上去的一个标志,告诉你说下面这个方法是从父类/接口 继承过来的,重写了一次
    • 可以当注释用,方便阅读
    • 编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错
    • 比如你如果没写@Override而你下面的方法名又写错了,这时你的编译器是可以通过的(它以为这个方法是你的子类中自己增加的方法)
  • class_name.class: 获取class_name的这个类的类名,返回值等同于object.getClass(),但是class_name对应的是类名,object对应的是实例名
  • 命令行编译java代码javac class.java; java class

返回值

  • 不要在程序中返回null值,如果对象为空就返回一个空的对象

    import java.util.Collections;
    public List<AppCommentModel> getAppComments(Integer page, Integer size, String appId) {
        if (null == appId || "".equals(appId)) {
            // 应该返回一个空列表而非null
            return Collections.emptyList();
        }

类的定义和使用

声明规则

当在一个源文件中定义多个类,并且还有import语句和package语句时,要特别注意这些规则.

  • 一个源文件中只能有一个public类
  • 一个源文件可以有多个非public类
  • 源文件的名称应该和public类的类名保持一致.例如: 源文件中public类的类名是Employee,那么源文件应该命名为Employee.java.
  • 如果一个类定义在某个包中,那么package语句应该在源文件的首行.
  • 如果源文件包含import语句,那么应该放在package语句和类定义之间.如果没有package语句,那么import语句应该在源文件中最前面.
  • import语句和package语句对源文件中定义的所有类都有效.在同一源文件中,不能给不同的类不同的包声明.

类基础

定义和使用类,是编写Java程序的必备

定义类的工作包括

  • 定义类的属性(也称为成员变量)
  • 定义类的方法
  • 定义类的构造方法(也称构造器或者构造函数)

使用类的工作包括

  • 基于类创建对象(即访问类的构造器)
  • 访问类的属性
  • 访问类的方法

成员变量的可见性

定义成员变量时,可以用private、protected或者public进行修饰,可以控制外部的可见性

  • public: 表示任何类都可以直接访问该成员变量
  • private: 表示任何其他类不能直接访问该成员变量,只有该类自身可以访问
  • protected: 表示只有该类自身及其子类,以及与该类同一个包的类,可以访问该成员变量
  • 没有修饰: 表示同一个包的类可以访问该成员变量

方法重载

方法名相同,但是参数不同的现象,称之为方法重载.方法的返回值和参数构成了方法的签名.

定义构造器

构造器用于创建对象.构造器类似于普通方法,但是有两个特殊的地方

  • 方法名称必须和类名相同
  • 不允许定义返回类型

如果你没有定义任何构造器,则编译器会自动帮你生成一个构造器,称之默认构造器

// 没有显示定义构造器,系统会默认给一个构造器因此我们可以用new来调用默认构造器,构造器也可以用`public`和`private`来修饰,用了`private`外部就不能用new方法来运行
class Post {
    private String title;
    private String content;
}
Post post = new Post();

class Post {
    private String title;
    private String content;
    // 构造器重载
    // 如果显式定义了构造器后,需要传相应的参数 Post post = new Post();的方法会报错,除非显式增加一个没有参数的构造器
    public Post(String title, String content) {
        // 如果类的方法或者构造器的参数与自己的成员变量重名的时候,就可以使用this来进行区别,但是
        this.title = title;
        this.content = content;
    }
}
Post post = new Post("文章的标题", "文章的内容");

class Post {
    private String title;
    private String content;

    // 第一个构造器
    public Post(String title) {
        this.title = title;
    }

    // 第二个构造器
    // 在这个构造器中,this(title);这一行代码表示调用第一个构造器. 在构造器比较复杂时,这种方式可以让代码更加简洁
    public Post(String title, String content) {
        this(title);
        this.content = content;
    }
}

初始化成员变量

class Post {
    // 成员变量可以直接赋值
    private String title = "默认标题";
    private String content = "默认内容";

    // 通过final关键字赋值
    private String content = initContent();
    private final String initContent() {
        return "默认内容";
    }

    // 通过构造块初始化
    {
        title = "默认标题";
        content = "默认内容";
    }
}

访问对象属性

类内部通过this来访问自身的属性,在外部也可以访通过对象名.属性名访问别的类非private属性

静态变量和静态方法

静态变量

类的成员变量中,用static修饰的变量称为静态变量或者类变量,而没有用static修饰的变量则是普通变量.

  • 普通变量: 创建类的实例就会创建该成员变量的一个拷贝,分配一次内存.由于成员变量是和类的实例绑定的,所以需要通过对象名进行访问,而不能直接通过类名对它进行访问.
  • 静态变量: 内存中只有一份,JVM只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配.由于静态变量属于类,与类的实例无关,因而可以直接通过类名访问这类变量.
public class Post {

    private String title;
    private String content;

    public static int count = 0;

    public Post(){
        count++;
    }
}
  • title和content是实例成员变量,每个Post实例都独立的拥有title和content属性,修改实例两个变量的值不会対其他实例有影响.
  • count是个静态变量.修改count的值时,所有实例的count值都会改变.这个例子是每当实例化一个Post对象,count值就会+1

静态方法

static除了可以修饰成员变量外,还可以修饰方案,表明声明的方法与某个具体实例无关,仅仅是该类的一个公共方法

public class Post {

    private String title;
    private String content;

    private static int count = 0;

    public Post(){
        count++;
    }

    public static int getCount(){
        return count;
    }
}

这里不希望count定义为公共属性,如果是公共属性任何地方都可以通过Post.count++来改变count值.只希望在实例化时使count值+1.因此可以将count声明为私有变量.同时又希望能通过Post类获取count属性,此时可以声明了静态的getCount方法,然后通过类名.静态方法名访问.

声明static方法的限制

  • 仅能调用static方法和static属性
  • 不能以任何方式引用this或super: 静态方法可以直接通过类名调用,任何的该类的实例也都可以调用它的静态方法,因此静态方法中不能用this和super关键字
  • 在一个static方法中引用任何实例变量都是非法的

静态代码块

类似于静态变量和静态方法,有static修饰的代码块称为静态代码块.

它独立于类成员,可以有多个,JVM加载类的时候会执行静态代码块,如果static代码块有多个,JVM则会按照它们在类中出现的顺序依次执行它们,且每个代码块只能执行一次.可以利用静态代码块可以对一些static变量进行赋值.

public class Post {

    private String title;
    private String content;

    // 如果Post数量是从数据库中取得的,那么初始化为1是不正确的选择,此时可以通过静态代码块在类加载时执行获取数据库中的值
    // 加载类的时候会先执行这里的代码
    static {
        count = 100; //这里假设100是从数据库中获取Post的数量
    }
}

泛型

JAVA是一个强类型的语言,一般情况下每个变量只能是一个类型.能否只定义一个类就能满足坐标既可能为int类型,也可能为double类型的情况呢

单个类型是泛型

// cat Point.java
public class Point<T> {
    private T x;
    private T y;

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public T getY() {
        return y;
    }

    public void setY(T y) {
        this.y = y;
    }
}

// cat Test.java
public class Test{
    public static void main(String[] args){

        // 坐标为int类型,把int类型的包装类Integer作为参数传入泛型类中
        Point<Integer> point1 = new Point<Integer>();
        point1.setX(1);
        point1.setY(1);

        // 坐标为double类型,把double类型的包装类Double作为参数传入泛型类中
        Point<Double> point2 = new Point<Double>();
        point2.setX(3.456);
        point2.setY(4.789);
    }
}

多个泛型类型

public class Triple<A, B, C> {
    private A a;
    private B b;
    private C c;
}

泛型方法

public class Printer {
    public static <T> void printArray(T[] objects) {
        if (objects != null) {
            for(T element : objects){
                System.out.printf("%s",element);
            }
        }
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Character[] charArray = {'A', 'B', 'C', 'D'};

        printArray(intArray);
        printArray(charArray);
    }
}

反射

反射是一种动态获取信息以及动态调用对象方法的机制.在程序运行状态中,通过反射能够知道某个类具有哪些属性和方法;能够访问某一个对象的方法和属性.具体来说,反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法
  • 生成动态代理

封装

封装是一种隐藏信息的技术,是将一个系统中的结构和行为通过类来划分的过程.即通过定义一组类,将特定的数据组合到某一个类中,形成一个整体,将该隐藏的数据进行保护,只对外暴露这些数据的访问的方法.封装代码有两个好处:

  • 代码使用者无需考虑实现细节就能直接使用它,同时不用担心不可预料的副作用,别人不能随便修改内部结构
  • 在外部接口保持不变的情况下,自己可以修改内部的实现

继承

继承是一种类和类之间的关系,是面向对象系统的基石.我们可以把共性的结构和行为放到父类中,子类可以通过继承复用父类中的代码,并且根据自己的需要进行扩展.使用extends关键字表示继承关系.利用继承的方法,可以重用已存在类的方法和属性,而不用重写这些代码.被继承的类称为超类(super class),派生类称为子类(subclass)

  • java的继承是单继承, 一个子类只能继承一个父类
  • 子类会继承父类所有非private方法和变量,除了构造函数
  • 生成子类对象或者实例时,Java默认地首先调用父类的不带参数的构造方法,接下来再调用子类的构造方法,生成子类对象
  • 方法覆盖: 子类中有和父类中非private的同名方法,且返回类型和参数表也完全相同,就会覆盖从父类继承来的方法.子类中通过super关键字调用父类被重写的方法
// cat Graph.java
class Graph {
    String name;
    public Graph(){}
    public Graph(String name) {
        this.name = name;
    }
    public void show() {
        System.out.println("I'm a graph");
    }
}

// cat Rectangle.java
class Rectangle extends Graph {
    int width;
    int height;

    public Rectangle(){
        // this表示对当前对象的引用
        // 子类构造函数中,一般第一条语句是`super();`,表示调用父类构造函数
        // 构造函数的第一语句既不是this()也不是super()时,就会隐含的调用super()
        super();
    }

    public Rectangle(String name) {
        super(name);
    }

    public Rectangle(int width, int height,String name) {
        // this表示对当前对象的引用
        this(name);
        System.out.println("My width is:" + width + "my height is :"+ height);
    }

    public void show() {
        // Retangle就覆盖了Graph的show方法,同时在show方法中通过super.show();调用了父类的show方法
        super.show();
        System.out.println("at the same time I'm a Rectangle");
    }
}

常量-final关键字

final修饰变量: 一个变量可以声明为final,这样做的目的是阻止内容被修改(类似于C/C++中的const),final变量的所有字符选择大写是一个普遍的编码约定,用final修饰的变量在实例中不占用内存,它实质上是一个常数

final修饰方法: 被final修饰的方法可以被子类继承,不能被子类的方法覆盖,因此,如果一个类不想让它的子类覆盖它的某个成员方法,就可以在该成员方法前面加上final关键字.由于父类中的private成员方法是不能被子类覆盖的,所有private的成员方法默认也是final的.使用final修饰方法除了不想让子类覆盖之外,还有一个原因就是高效,Java编译器在遇到final关键字修饰的方法时会使用内联机制,省去函数调用的开销,提高效率

final修饰类: 由final修饰的类是不能继承

抽象类与接口

抽象类

在面向对象的领域一切都是对象,所有的对象都是通过来描述的.抽象类是指类中没有足够的信息描述对象,需要别的类扩展.抽象类需要在类定义的前面增加abstract关键字.

abstract class Graph {
    String name;
    public Graph(){}
    public Graph(String name) {
        this.name = name;
    }
    public void show() {
        System.out.println("I'm a graph");
    }
}

abstract关键字同样可以用来声明抽象方法,抽象方法只包含方法名,而没有方法体.抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号.声明抽象方法会带来以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类.
  • 任何子类必须重写父类的抽象方法,否则就必须声明自身为抽象类

一般情况下,我们将一个类声明为abstract的,是因为它包含了没有具体实现的抽象方法.比如说我们给Graph类增加一个求面积的方法area(),因为我们不知道图形的形状,我们是无法给出实现的,只能交给特定的子类去实现,这时我们只能将area()声明为abstract的,代码如下

abstract class Graph {
    String name;
    public Graph(){}
    public Graph(String name) {
        this.name = name;
    }
    public void show() {
        System.out.println("I'm a graph");
    }
    public abstract double area();
}

// 这时Rectangle类就必须给出area()方法的实现,否则它自己也必须用abstract修饰

class Rectangle extends Graph{
    double width;
    double height;
    public double area() {
        return width * height;
    }
}

接口

接口Interface是一组抽象方法的集合.接口中定义的方法没有方法体.接口也和抽象类一样,无法被实例化,但是可以被实现(implements).一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类.接口中的方法都是外部可访问的,因此我们可以不需要用public修饰.接口中也可以声明变量,一般是finalstatic类型的,要以常量来初始化,实现接口的类不能改变接口中的变量

  • 接口中的变量默认是public, static, and final,[参考][2],所以不需要再写public static final VARIABLE = XXX;
interface Animal {
    static final int TIMES_OF_EATING = 3;
    void eat();
    void sleep();
}
// 接口可以继承其他的接口
interface TerrestrialAnimal extends Animal {
    void run();
}

接口实现

类使用implements关键字实现接口,类需要对接口中的每一个方法都给出实现.

class Cat implements Animal {
    public void eat() {
        System.out.println("eat");
    }
    public void sleep() {
        System.out.println("sleep");
    }
}

抽象类和接口的比较

相同点:

  • 都不能被实例化
  • 都包含抽象方法,用于描述系统能提供哪些服务,由子类来提供实现具体的服务
  • 在系统设计上,两者都代表系统的抽象层,当一个系统使用一棵继承树上的类时,应该尽量把引用变量声明为继承树的上层抽象类型,这样可以提高两个系统之间的松耦合

不同点:

  • 在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们;但是抽象类不支持多继承.接口不能实现任何方法,但是支持多继承.
  • 接口是定义者和实现者的一种契约;而抽象类和具体类一般而言是一种is-a的关系,即两者在概念本质上是不同的.

异常处理

throw, throws, try和catch的区别

  • throw: 应用在方法体中,用于抛出异常。当方法在执行过程中遇到异常情况时,将异常信息封装为异常对象,然后向调用方法对象throw
  • throws: 应用在方法的声明中,表示该方法可能会抛出的异常,允许throws后面跟着多个异常类型
  • try和catch: 应用在代码块中,当部分代码可能存在异常时使用,将肯能存在异常的代码放在try代码块中,在catch只处理已知且可能出现的异常

    正确的个人理解是,不要无意义的乱用try,catch,不能让这个来作为业务的一部分,除非有些情况下它的确可以,比如判断输入的字符串是不是可以转化为int,如果不可以,则赋值0,因为这是业务上需要的,但如果业务不允许赋值0,那么你就应该将异常抛出,而不是自作主张的catch,反正这个是一个需要把握的问题,一般情况下,应当将异常层层抛出,由最外层来决定如何处理异常 其实很简单,过程不可控的时候,如网络通信,中间网络是否稳定是软件所无法控制的,可以try。如本地操作数据库或者文件,都是可控的,不要用try 关于Try,Catch的正确用法8楼写得很好,用了try catch但是没有对异常进行处理,只是单纯抛出异常会导致异常的StackTrace信息混乱,所以如果对异常不处理一般不catch异常,如果在mvc的controller层做日志记录,可以catch (Exception e) {log.error("接口调用异常"); throw;};

try与catch解决程序异常

public class HelloWorld {
    public static void main(String[] args) {
        // try代码有异常 异常点以后代码不执行 直接执行catch以及之后的代码
        try {
            int x = 5 / 0;
            System.out.println(x);
        } catch (Exception e) {
            // 展开错误栈
            e.printStackTrace();
        }
        System.out.println("program is still running here!");
    }
}

catch多个异常

public class HelloWorld {

    public static void main(String[] args) {
        try {
            foo();
            bar();
        // 分别按照顺序查看多个异常,当有一个异常匹配中的时候就按照顺序检查异常
        } catch (BlogAppException e) {
            e.printStackTrace();
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("program is still running here!");
    }
}

finally关键字

在try/catch语句后,我们还可以有个finally语句,finally语句中的代码块不管异常是否被捕获总是要被执行的.我们将上面的程序作如下修改,来看看finally语句的用法与作用

public class HelloWorld {

    public static void main(String[] args) {
        try {
            foo();
            bar();
        } catch (BlogAppException e) {
            e.printStackTrace();
            return;
        } catch (ArithmeticException e) {
            e.printStackTrace();
            return; // 即使这里return了,依然会执行finally中的语句
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("programe will run `finally` code block!");
        }
        System.out.println("program is still running here!");
    }
}

throws

如果一个方法中的语句执行时可能生成某种异常,但是并不能确定如何处理,则此方法应声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理

public class HelloWorld {
    // throws关键字将方法异常交给其调用者
    // throws的异常应该是更加具体粒度更加细的
    private static void foo() throws Exception {
        int x = 5 / 0;
        System.out.println(x);
    }

    public static void main(String[] args) {
        try {
            foo();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("program is still running here!");
    }
}

常见异常

  • ArithmeticException(在算术运算中发生的异常,如除以零)
  • NullPointerException(变量还没有指向一个对象,就引用这个对象的成员)
  • ArrayIndexOutOfBoundsException(访问数组对象中不存在的元素)
  • IllegalArgumentException: 参数为空,确保参数的值不为空IllegalArgumentException or NullPointerException for a null parameter

自定义异常

用户自定义异常类,只需继承Exception类即可.在程序中使用自定义异常类,大体可分为以下几个步骤:

  • 创建自定义异常类
  • 在方法中通过throw关键字抛出异常对象
  • 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字向方法调用者抛出异常
  • 在出现异常方法的调用者中捕获并处理异常
// cat BlogAppException.java
public class BlogAppException extends Exception {

    private static final long serialVersionUID = 1L;

    private String command;// 可以给自定义异常增加成员变量,用以保存额外的异常信息

    public BlogAppException(String command) {
        this.command = command;
    }

    public String toString(){
        return "Exception happened when executing command " + command;
    }
}

// cat HelloWorld.java
public class HelloWorld {

    private static void bar() throws BlogAppException {
        System.out.println("let's assume BlogAppException happened when executing `create` command");
        // 为了演示,这里我们假设执行create命令时,抛出了异常
        throw new BlogAppException("create");
    }

    private static void foo() throws ArithmeticException {
        int x = 5 / 0;
        System.out.println(x);
    }

    public static void main(String[] args) {
        try {
            foo();
            bar();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("program is still running here!");
    }
}

FAQ

JAVA如何调用类

在src文件夹中先定义各个类,然后再生成一个主类的入口,在这个主类入口中调用全部要用到的类

// cat Post.java
public class Post {
    String title; // 成员变量
    String content; // 成员变量
    // 成员方法
    void print() {
        System.out.println(title);
        System.out.println(content);
    }
}

// cat HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        Post post = new Post();    // 创建博客对象
        post.title = "title";      // 对象属性赋值
        post.content = "content";  // 对象属性赋值
        post.print();              // 调用对象方法
    }
}

获取控制台输出

为了获取控制台输入,首先需要一个创建一个Scanner对象,创建Scanner对象时需要用System.in作为它的参数,Scanner从System.in取得用户输入的内容后

  • next(): 取得一个字符串
  • nextLine(): 读取一整行内容
  • hasNext(): 判断是否还有输入
  • nextInt(): 将取得的字符串转换成int类型的整数
  • nextFloat(): 将取得的字符串转换成float型
  • nextBoolean(): 将取得的字符串转换成boolean型

使用Scanner非常方便,但也有不足.Scanner取得输入的依据是空格符,包括空格键、Tab键和回车键等.当按下这些键时,Scanner就会返回下一个输入.因此当你输入的内容中间包括空格时,显然使用Scanner不能完整的获得你输入的字符串.

java中堆和栈的区别

堆和栈都是Java中常用的存储结构,都是内存中存放数据的地方

  • 在方法中定义的基本类型变量和引用类型变量,其内存分配在栈上,变量出了作用域(即定义变量的代码块)就会自动释放
  • 堆内存主要作用是存放运行时通过new操作创建的对象

关于栈和堆更加形象的介绍在,引用类型变量指的是这个变量存储在栈中,但是其成员变量及里面的明细全部保留在堆中,可以直观地认为myCar变量保存的就是所创建对象在堆中的地址0x6E34,即myCar引用了一个对象,这正是引用类型变量这个叫法的原因

基本的数据类型的值存储在栈中,作用域结束这些在栈中的内存会自动释放

判断对象是否为空

判断对象是否为空 How to check if object is null or not except == null

获取文件的扩展名

How do I get the file extension of a file in Java

// 一般文件 如 archive.tar.gz
String extension = "";
int i = fileName.lastIndexOf('.');
// 保证没有扩展名的文件不会报错
if (i > 0) {
    extension = fileName.substring(i + 1);
}

// 特殊文件 如 /path/to.a/file
String extension = "";
int i = fileName.lastIndexOf('.');
int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
if (i > p) {
    extension = fileName.substring(i + 1);
}

i++和++i的区别

  • int i=1, a=0;
  • i++ 先运算在赋值,例如 a=i++,先运算a=i,后运算i=i+1,所以结果是a==1
  • ++i 先赋值在运算,例如 a=++i,先运算i=i+1,后运算a=i,所以结果是a==2

类型转换提醒

Idea提醒: Type safety: Unchecked cast from Object to HashMap, 出现这个提醒的目的是为了保证变量类型安全,参考这里

// 可以在报错提醒之前使用`@SuppressWarnings("unchecked")`解决
@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

为java的函数设置默认值

is it possible to declare default argument in java in string

启动java时指定路径加载类

java -cp $CLASSPATH -cp参数后面是类路径,是指定给解释器到哪里找到你的.class文件

interface不需要写public static final

link


⚠️ **GitHub.com Fallback** ⚠️