Java是SUN公司的詹姆斯·高斯林在90年代初开发的一种编程语言后来被Oracle收购,随着互联网的高速发展,Java逐渐成为最重要的编程语言。对于软件测试来说,掌握一两门编程语言已是必要条件,具体学习哪门编程语言还需根据自己的工作需求以及兴趣爱好来考虑。下图是TIOBE统计的编程语言长期历史排名:

img

基础语法

二维数组

二维数组就是数组的数组,三维数组就是二维数组的数组;

多维数组的每个数组元素长度都不要求相同;

打印多维数组可以使用Arrays.deepToString()

最常见的多维数组是二维数组,访问二维数组的一个元素使用array[row][col]

二维数组遍历:

import java.util.Arrays;

public class Myclass {
    public static int [][] ns = {
            {1,3,4,2},
            {5,6},
            {7,9,8}
    };
    public void forMethod(){
        for(int i = 0; i < ns.length; i++){
            for (int j = 0; j < ns[i].length; j++){
                System.out.print(ns[i][j] + ",");
            }
        }
    }
    public void foreachMethod(){
        for (int arr[]: ns) {
            for(int n: arr){
                System.out.print(n + ",");
            }
        }
    }

    public static void main(String[] args) {
        Myclass m = new Myclass();
//        m.forMethod();
//        m.foreachMethod();
        System.out.println(Arrays.deepToString(ns));
    }
}

面向对象基础

方法参数绑定

  • 基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
  • 引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方,因为指向同一个对象。
package com.lsaiah.java;

public class MethodDemo {
    public static void main(String[] args) {
        Person p = new Person();
        int n = 10;
        p.setAge(n); //传入n的值10
        n = 15; //n的值改为15
        p.setName("张三");
        //基本类型参数绑定:输出p.getAge()还是10
        //当set传递数组时,get指向同一个对象,当修改传递时对象改变
        System.out.println("姓名:" + p.getName() + " 年龄:" + p.getAge());
    }
}

class Person {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private String name;
    private int age;
}

构造方法、封装

  • 实例在创建时通过new操作符会调用其对应的构造方法,构造方法用于初始化实例,方法名就是类名,没有返回值;
  • 没有定义构造方法时,编译器会自动创建一个默认的无参数构造方法;
  • 可以定义多个构造方法,编译器根据参数自动判断;
  • 可以在一个构造方法内部调用另一个构造方法,便于代码复用。
package com.lsaiah.java;

public class MethodDemo02 {
    public static void main(String[] args) {
    //TODO:请给Person02类增加(String, int)的构造方法:
        Person02 p = new Person02("张三", 10);
        System.out.println("姓名:" + p.getName() + " 年龄:" + p.getAge());
    }
}

class Person02 {
    public Person02(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    private String name;
    private int age;
}

方法重载

  • 方法重载是指多个方法的方法名相同,但各自的参数不同;
  • 重载方法应该完成类似的功能,参考StringindexOf()
  • 重载方法返回值类型应该相同。
public class MethodDemo03 {
    public static void main(String[] args) {
        // TODO: 给Person增加重载方法setName(String, String):
        Person03 ming = new Person03();
        Person03 hong = new Person03();
        ming.setName("Xiao Ming");
        hong.setName("Xiao", "Hong");
        System.out.println(ming.getName());
        System.out.println(hong.getName());
    }
}

class Person03 {
    public String getName() {
        return name;
    }
    //方法重载:方法名、返回值类型相同,参数类型数量不同
    public void setName(String name) {
        this.name = name;
    }
    public void setName(String name, String name1) {
        this.name = name + " " + name1;
    }
    private String name;
}

继承

  • 继承是面向对象编程的一种强大的代码复用方式;
  • Java只允许单继承,所有类最终的根类是Object
  • protected允许子类访问父类的字段和方法;
  • 子类的构造方法可以通过super()调用父类的构造方法;
  • 可以安全地向上转型为更抽象的类型;
  • 可以强制向下转型,最好借助instanceof判断;
  • 子类和父类的关系是is,has关系不能用继承。
//instanceof variable: 从Java 14开始支持
//在不支持的版本中编译:javac --enable-preview -source 14 Main.java 
public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // 可以直接使用变量s:
            System.out.println(s.toUpperCase());
        }
    }
}
package com.lsaiah.java;

public class ExtendsDemo {
    public static void main(String[] args) {
        Person p = new Person("张三", 20);
        Student s = new Student("李四", 15, 60);
        PrimaryStudent ps = new PrimaryStudent("张三", 10, 100, 5);
        System.out.println("姓名"+ ps.getName() + "年龄"+ps.getAge() + "分数"+ ps.getScore() + "年级"+ps.getGrade());
    }
}

class Person {
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    protected String name;
    protected int age;
}

class Student extends Person{
    public int getScore() {
        return score;
    }
    public Student(String name, int age, int score){
        super(name,age);
        this.score = score;
    }

    protected int score;
}

class PrimaryStudent extends Student{
    public int getGrade() {
        return grade;
    }
    protected int grade;

    public PrimaryStudent(String name, int age, int score,int grade) {
        super(name, age, score);
        this.grade = grade;
    }
}

多态

  • 子类可以重写父类的方法(Override),重写在子类中改变了父类方法的行为;
  • Java的方法调用总是作用于运行期对象的实际类型,这种行为称为多态;
  • final修饰符有多种作用:
    • final修饰的方法可以阻止被重写;
    • final修饰的class可以阻止被继承;
    • final修饰的field必须在创建对象时初始化,随后不可修改。
package com.lsaiah.java;

//TODO:给一个有工资收入和稿费收入的小伙伴算税。
public class PolymorphicDemo {
    public static void main(String[] args) {
        Income[] incomes = new Income[] {
                new Income(3000),
                new Salary(7500),
                new Royalty(15000)
        };
        double totalTax = 0;
        for (Income income : incomes){
            totalTax+=income.getTax();
        }
        System.out.println(totalTax);
    }
}

class Income {
    //Income构造方法,传递参数income
    public Income(double income) {
        this.income = income;
    }
    //Income类计算税率的方法
    public double getTax(){
        return income * 0.1;
    }
    protected double income;
}

class Salary extends Income{

    public Salary(double income) {
        super(income);
    }
    @Override
    public double getTax() {
        if (income < 5000){
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

class Royalty extends Income{

    public Royalty(double income) {
        super(income);
    }
    @Override
    public double getTax() {
        return 0;
    }
}

抽象类

  • 通过abstract定义的方法是抽象方法,它只有定义,没有实现。抽象方法定义了子类必须实现的接口规范;
  • 定义了抽象方法的class必须被定义为抽象类,从抽象类继承的子类必须实现抽象方法;
  • 如果不实现抽象方法,则该子类仍是一个抽象类;
  • 面向抽象编程使得调用者只关心抽象方法的定义,不关心子类的具体实现。
package com.lsaiah.java;

//TODO:用抽象类给一个有工资收入和稿费收入的小伙伴算税。
public class abstractDemo {
    public static void main(String[] args) {
        Income01[] incomes01 = new Income01[]{
          new Income01(3000) {
              @Override
              double getTax() {
                  return income * 0.1;
              }
          },
          new Salary01(7500),
          new Royalty01(15000)
        };
        double totalTax = 0;
        for (Income01 income01:incomes01) {
            totalTax+=income01.getTax();
        }
        System.out.println(totalTax);
    }
}

abstract class Income01{
    protected double income;
    public Income01(double income){
        this.income = income;
    };
    abstract double getTax();
}

class Salary01 extends Income01{
    public Salary01(double income) {
        super(income);
    }
    @Override
    double getTax() {
        if (income < 5000){
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

class Royalty01 extends Income01{
    public Royalty01(double income) {
        super(income);
    }
    @Override
    double getTax() {
        return 0;
    }
}

接口

Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;

接口也是数据类型,适用于向上转型和向下转型;

接口的所有方法都是抽象方法,接口不能定义实例字段;

接口可以定义default方法。

abstract class interface
继承 只能extends一个class 可以implements多个interface
字段 可以定义实例字段 不能定义实例字段
抽象方法 可以定义抽象方法 可以定义抽象方法
非抽象方法 可以定义非抽象方法 可以定义default方法
package com.lsaiah.java;

//TODO:用接口给一个有工资收入和稿费收入的小伙伴算税。
public class InterfaceDemo {
    public static void main(String[] args) {
        Income02[] incomes02 = new Income02[]{
                new Salary02(7500),
                new Royalty02(15000)
        };
        double totalTax = 0;
        for (Income02 income02: incomes02) {
            totalTax+=income02.getTax();
        }
        System.out.println(totalTax);
    }
}

interface Income02 {
    double getTax();
}

class Salary02 implements Income02{
    private double income;
    public Salary02(double income){
        this.income = income;
    }
    @Override
    public double getTax() {
        if (income < 5000){
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}
class Royalty02 implements Income02{
    private double income;
    public Royalty02(double income){
        this.income = income;
    }
    @Override
    public double getTax() {
        return 0;
    }
}

静态字段和静态方法

  • 静态字段属于所有实例“共享”的字段,实际上是属于class的字段;
  • 调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法;
  • 静态方法常用于工具类和辅助方法,如Arrays.sort();。
package com.lsaiah.java;

//TODO:给Person04类增加一个静态字段count和静态方法getCount,统计实例创建的个数
public class StaticDemo {
    public static void main(String[] args) {
        Person04 p4 = new Person04("张三");
        System.out.println(Person04.getCount());
        System.out.println(Person04.getCount());
        Person04 p5 = new Person04("李四");
        System.out.println(Person04.getCount());
        Person04 p6 = new Person04("王五");
        System.out.println(Person04.getCount());
    }
}

class Person04 {
    public Person04(String name) {
        this.name = name;
        count++;
    }

    public static int getCount() {
        return count;
    }

    static int count;
    static String name;
}

包/API

  • Java内建的package机制是为了避免class命名冲突;
  • JDK的核心类使用java.lang包,编译器会自动导入;
  • JDK的其它常用类定义在java.util.*java.math.*java.text.*,……;
  • 包名推荐使用倒置的域名,例如org.apache
  • 使用完整类名或使用import语句导入
  • JAVA 提供API:https://docs.oracle.com/javase/8/docs/api/

作用域

  • Java内建的访问权限包括publicprotectedprivatepackage权限;
  • Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
  • final修饰符不是访问权限,它可以修饰classfieldmethod
  • 一个.java文件只能包含一个public类,但可以包含多个非public类。

访问修饰符

修饰类:

  • public:该类可以被任何其他类访问
  • default: 该类只能由同一包中的类访问。当不指定修饰符时使用。

修饰属性,方法和构造函数:

  • public:该属性方法构造函数可用于所有类
  • private: 该属性方法构造函数只能在声明的类中访问
  • default: 该属性方法构造函数只能在同一程序包中访问。当不指定修饰符时使用。
  • protected:该属性方法构造函数可在相同的包和子类中访问。

非访问修饰符

修饰类:

  • final: 该类不能被其他类继承
  • abstract: 该类不能用于创建对象(要访问抽象类,它必须从另一个类继承。

修饰属性,方法和构造函数:

  • final: 属性和方法不能被覆盖/修改
  • static: 属性和方法属于类,而不是对象,调用需要先对象实例化
  • abstract: 只能在抽象类中使用,并且只能在方法上使用。该方法没有主体,例如abstract void run(); 主体由子类提供(从继承)。
  • transient: 序列化包含属性和方法的对象时,将跳过这些属性和方法。
  • volatile:属性的值不在线程本地缓存,并且始终从“主内存”中读取

classpath和jar

classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。Jar包的第一层目录不能是bin目录。

  • JVM通过环境变量classpath决定搜索class的路径和顺序;
  • 不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;
  • jar包相当于目录,可以包含很多.class文件,方便下载和使用;
  • MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。

模块

  • Java 9引入的模块目的是为了管理依赖;
  • 使用模块可以按需打包JRE;
  • 使用模块对类的访问权限有了进一步限制。

打包Jar包:

$ jar --create --file hello.jar --main-class com.itranswarp.sample.Main -C bin .

运行Jar包:

java -jar hello.jar

打包模块:

$ jmod create --class-path hello.jar hello.jmod

运行模块:

java --module-path hello.jar --module hello.world

打包JRE:

$ jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/

运行JRE:

$ jre/bin/java --module hello.world

核心类

字符串和编码

  • Java字符串String是不可变对象;
  • 字符串操作不改变原字符串内容,而是返回新字符串;
  • 常用的字符串操作:提取子串、查找、替换、大小写转换等;
  • Java使用Unicode编码表示Stringchar
  • 转换编码就是将Stringbyte[]转换,需要指定编码;
  • 转换为byte[]时,始终优先考虑UTF-8编码。

StringBuilder

  • StringBuilder是可变对象,用来高效拼接字符串;
  • StringBuilder可以支持链式操作,实现链式操作的关键是返回实例本身;
  • StringBufferStringBuilder的线程安全版本,现在很少使用。
package com.lsaiah.java;

//TODO:请使用StringBuilder构造一个INSERT语句
public class StringBuilderDemo {
    public static void main(String[] args) {
        String[] fields = { "name", "position", "salary" };
        String table = "employee";
        String insert = buildInsertSql(table, fields);
        System.out.println(insert);
        String s = "INSERT INTO employee (name, position, salary) VALUES (?, ?, ?)";
        System.out.println(s.equals(insert) ? "测试成功" : "测试失败");
    }
    static String buildInsertSql(String table, String[] fields) {
        StringBuilder sb = new StringBuilder(1024);
        sb.append("INSERT INTO ").append(table).append(" (");
        for (int i = 0; i < fields.length; ++i) {
            if (i == fields.length - 1) {
                sb.append(fields[i]);
                continue;
            }
            sb.append(fields[i] + ", ");
        }
        sb.append(") VALUES (?, ?, ?)");
        return sb.toString();
    }
}

StringJoiner

  • 用指定分隔符拼接字符串数组时,使用StringJoiner或者String.join()更方便;
  • StringJoiner拼接字符串时,还可以额外附加一个开头和结尾。
package com.lsaiah.java;
import java.util.StringJoiner;

public class StringJoinerDemo {
    public static void main(String[] args) {
        String[] fields = { "name", "position", "salary" };
        String table = "employee";
        String select = buildSelectSql(table, fields);
        System.out.println(select);
        System.out.println("SELECT name, position, salary FROM employee".equals(select) ? "测试成功" : "测试失败");
    }

    private static String buildSelectSql(String table, String[] fields) {
        var sj = new StringJoiner(", ", "SELECT "," FROM " + table);
        for (String s : fields){
            sj.add(s);
        }
        return sj.toString();
    }
}

包装类型

Java是一种面向对象的编程语言,而基本数据类型的值不是对象,将简单数据类型的数据进行封装而得到的类就是包装类。
下表显示了原始类型和等效的包装器类:

基础数据类型 引用类型
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
boolean java.lang.Boolean
char java.lang.Character
  • Java核心库提供的包装类型可以把基本类型包装为class
  • 自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);
  • 装箱和拆箱会影响执行效率,装箱是将基础数据类型变为引用类型的赋值,拆箱是将引用类型变为基础数据类型可能发生NullPointerException
  • 包装类型的比较必须使用equals()
  • 整数和浮点数的包装类型都继承自Number
  • 包装类型提供了大量实用方法。

JavaBean

  • JavaBean是一种符合命名规范的class,它通过gettersetter来定义属性;
  • 属性是一种通用的叫法,并非Java语法规定;
  • 可以利用IDE快速生成gettersetter
  • 使用Introspector.getBeanInfo()可以获取属性列表。
package com.lsaiah.java;
import java.beans.*;
public class Main {
    public static void main(String[] args) throws Exception {
        BeanInfo info = Introspector.getBeanInfo(Person.class);
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            System.out.println(pd.getName());
            System.out.println("  " + pd.getReadMethod());
            System.out.println("  " + pd.getWriteMethod());
        }
    }
}

class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

枚举类

枚举类是一种特殊类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个接口,但枚举类不能继承其他类。

  • Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum { … }
  • 通过name()获取常量定义的字符串,注意不要使用toString()
  • 通过ordinal()返回常量定义的顺序(无实质意义);
  • 可以为enum编写构造方法、字段和方法
  • enum的构造方法要声明为private,字段强烈建议声明为final
  • enum适合用在switch语句中。
public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
//        if (day.dayValue == 6 || //day.dayValue == 0) {
//            System.out.println("Today is //" + day + ". Work at home!");
//        } else {
//            System.out.println("Today is //" + day + ". Work at office!");
//        }
       switch(day){
           case MON:
           case TUE:
           case WED:
           case FRI:                            System.out.println("Today is " + day + ". Work at office!");
            break;
           case SAT:
           case SUN:
      System.out.println("Today is " + day + ". Work at home!");
            break;
           default:
               throw new RuntimeException("cannot process " + day);
       }
    }
}

enum Weekday {
    MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");

    public final int dayValue;
    private final String chinese;

    private Weekday(int dayValue, String chinese) {
        this.dayValue = dayValue;
        this.chinese = chinese;
    }

    @Override
    public String toString() {
        return this.chinese;
    }
}

记录类

从Java 14开始,提供新的record关键字,可以非常方便地定义Data Class:

  • 使用record定义的是不变类;
  • 可以编写Compact Constructor对参数进行验证;
  • 可以定义静态方法。

BigInteger

  • BigInteger用于表示任意大小的整数;
  • BigInteger是不变类,并且继承自Number
  • BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确。

BigDecimal

  • BigDecimal用于表示精确的小数,常用于财务计算,scale()表示小数位数,setScale传递(小数位数,RoundingMode.HALF_UP)表示四舍五入,(小数位数, RoundingMode.DOWN)表示直接截断,stripTrailingZeros() 方法将去除末尾的0,divideAndRemainder()方法返回商和余数的数组;
  • 比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()。返回负数、正数和0,分别表示大于、小于和等于。

常用工具类

Java提供的常用工具类有:

  • Math.abs():绝对值计算
  • Math.pow():次方计算
  • Math.sqrt():根号计算
  • Math.log():底数计算
  • Random:生成伪随机数
  • SecureRandom:生成安全的随机数

实际上真正的真随机数只能通过量子力学原理来获取,SecureRandom无法指定种子,它使用RNG(random number generator)算法。JDK的SecureRandom实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器,SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。在密码学中,安全的随机数非常重要。如果使用不安全的伪随机数,所有加密体系都将被攻破。因此,时刻牢记必须使用SecureRandom来产生安全的随机数。

import java.util.Arrays;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public class Main {
    public static void main(String[] args) {
        SecureRandom sr = null;
        try {
            sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
        } catch (NoSuchAlgorithmException e) {
            sr = new SecureRandom(); // 获取普通的安全随机数生成器
        }
        byte[] buffer = new byte[16];
        sr.nextBytes(buffer); // 用安全随机数填充buffer
        System.out.println(Arrays.toString(buffer));
    }
}

内部类

嵌套类(类中的一个类),要访问内部类需要先创建外部类的对象,然后创建内部类的对象。
示例:

class OuterClass {
    int x = 10;
    class InnerClass {
        int y = 5;
        public int myInnerMethod() {
            return x;
        }
    }
}
public class MyMainClass {
    public static void main(String[] args) {
        OuterClass myOuter = new OuterClass();
        OuterClass.InnerClass myInner = myOuter.new InnerClass();
        System.out.println(myInner.y + myOuter.x);
        System.out.println(myInner.myInnerMethod());
    }
}

当Private修饰内部类时,外部对象将无法访问内部类。
当Static修饰内部类时,可以不创建外部类对象访问内部类,无法访问外部类的成员。

Last modification:May 20th, 2020 at 11:12 pm
如果觉得我的文章对你有用,请随意赞赏,感谢!