文章目录
结束思维训练
在之前十讲中,我们学习了简单的逻辑,进行面向对象编程的训练,其中实用的东西看起来比较少,这是因为那些东西并不是针对使用的,主要目的是为了锻炼你思考的方式,所做的习题也都是为了帮助你养成编程思维
现在我们将要开始学习Java的常用类库,学习实用的编程
泛型
泛型,即“参数类型化”,在之前讲过声明一个变量必须给定它的类型,而使用泛型,则可以暂时不声明它的类型,而是在调用时再传入具体的类型
泛型类
通过泛型类,能够生成一个未定的数据类型
class ClassName<T>{
private T data;
}
让我们分析一个实例:泛型Student类
class Student<A>{
private String name;
private A data;
}
此时对着类中空行处按下alt+insert选择Getter and Setter,并选择全部
此时就会生成我们定义的两个属性的get和set方法
class Student<A>{
private String name;
private A data;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public A getData() {
return data;
}
public void setData(A data) {
this.data = data;
}
}
注意在data的方法中,data的参数类型都为自定的A
在生成这个类的对象时,通过如下方式定义,A的类型会变为在<>中的类型
(注意此处不能使用基本类型,必须使用它们的包装类)
Student<String> student1 = new Student<>();
Student<Integer> student2 = new Student<>();
Student<Float> student3 = new Student<>();
泛型接口
泛型接口通过如下方式定义
interface InterfaceName<T>{
T getData();
}
实现该接口时,可以直接指定参数类型
class Interface1 implements InterfaceName<String> {
private String data;
@Override
public String getData() {
return data;
}
}
也可以不指定
class Interface1<T> implements InterfaceName<T> {
private T data;
@Override
public T getData() {
return data;
}
}
泛型方法
通过如下方式定义一个泛型方法:
权限修饰符 <T> 返回值类型 方法名(参数列表){}
public static <T> void method(T t){}
泛型方法能传入任何类型的参数
泛型限定符和通配符
在使用泛型时,我们可以限定泛型的使用范围,即除声明的类之外无法使用,如
< T extends className>
< T extends interface1 & interface2>
泛型也可以用?作为通配符,代替具体的类型
< ? extends 父类名>
< ? super 子类名>
< ?>
这样可以限定泛型类型的上下届
泛型详解
泛型的主要作用:
- 提高代码的复用率
- 类型安全(使用时指定,不需要强制转化)
(特别提示:编译之后程序会采取一种名为去泛型化的措施,泛型无法存在于运行阶段,即在泛型被使用后会更改为指定类型,这也是和Object类型的主要区别)
常用类
String类
String其实也是一个类,java中所有的字符串其实都是String类的对象/实例,String通过数组的方式实现了不同字符的拼接,且java中所有用双引号""包含的内容都是字符串
String很特殊的一点是,如果两个字符串完全相等,则他们会采用完全相同的地址(与一般类不同),下面我将详细解释
让我们先了解一下关于堆的知识,在之前我们讲过堆中主要用于存储对象,堆内存在逻辑上主要分为三块,新生代(Young Generation),老年代(Old Generation),和永久代(Permanent Gerneration,也可以被成为原空间,Metaspace);新生代中存储着刚创建的对象和经过小于15次GC回收仍然存在的对象,而大于15次GC仍然存在的对象则会存在于老年代中,在新生代中GC会比老年代中要快的多。而永久代中则存放JDK自带的类和接口,不会被GC扫描,也不会被回收,内存只要JVM运行就被占用。
字符串常量池(存储单个字符的地方)会被存于方法区中,可以被所有线程共同实用,而方法区实际上就是堆内存的永久代区域,在物理上就是堆的一部分,为了节省这一部分的内存,相同的字符串就会使用相同的地址
字符串的值其实是不变的,它们的值在创建后无法更改,而我们进行的字符串操作其实都是产生了新的字符串
所以每次拼接字符都会产生一个新的字符串,所以如果多次拼接,就会产生许多垃圾,并且此垃圾在永久代中无法被回收,为了大量拼接字符串我们可以先创建StringBuilder或StringBuffer的对象,使用其中的append方法进行拼接,再用 对象名.toString() 将该对象转化为字符串来减小内存消耗(Builder和Buffer在不涉及线程时完全一样;而他们又经常迭代更新,这里我们就不详细解释了)
java.util.Objects
Object类我们之前介绍过,在java.lang中,是所有类的基类,并且重写过其中的toString和equals方法。
而Objects类在java.util包中,所以需要
import java.util.Objects;
(特别注意:除了在java.lang包中的类都需要额外import)
Objects类中有许多基础的方法,比如equals方法
在通常比较字符串时,我们使用equals方法
String s1 = "123";
String s2 = "234";
System.out.println(s1.equals(s2));
在一般的使用中没有问题,但是如果s1是null,那么就会报错
所以我们使用Objects类中的equals方法来比较字符串
String s1 = "123";
String s2 = "234";
System.out.println(Objects.equals(s1,s2));
其中加了非空的检测,可以避免空指针异常
java.lang.Math
Math类中有很多数字运算的方法,比如
Math.abs(num1);// 求绝对值
Math.min(num1, num2);// 返回两数中较小的值
Math.max(num1, num2);// 返回较大的
Math.round(num1);// 四舍五入
Math.floor(num1);// 返回小于等于参数的最大整数
Math.ceil(num1);// 返回大于等于参数的最小整数
Math.random();// 返回double类型随机数,范围为(0, 1)
(特别注意:100.5的四舍五入返回101,而-100.5的四舍五入返回-100)
java.util.Arrays
Arrays类中有许多用于操作数组的方法,包括我们之间实现过的二分查找,Arrays类中有很多不同的查找,排序,和表示方法
System.out.println(Arrays.toString(arr1));
// 打印整个数组,用逗号隔开
Arrays.sort(arr1);// 排序该数组
Arrays.binarySearch(arr1, target);// 返回查找下标
Arrays.copyOf(arr1, 15);// 创建一个新数组,拥有原先的元素且长度变为15
java.math.BigDecimal
运算以下代码
double a = 0.1 + 0.1;
double b = 0.1 + 0.2;
System.out.println(a);
System.out.println(b);
你会发现a的值为0.2,但是b的值却并不是0.3,而是0.30000000000000004,在这里我们就不具体解释原因,我们只需要知道在进行小数运算时很可能出现这种误差
(可以通过如下网址详细了解:0.1 + 0.2 为什么不等于 0.3 )
此时为了获取更准确的结果,我们就需要用到BigDecimal类中的方法
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
// BigDecimal bd3 = new BigDecimal("asd"); 将会报错
BigDecimal a = bd1.add(bd2);
BigDecimal b = bd1.substract(bd2);
BigDecimal c = bd1.multiply(bd2);
BigDecimal d = bd1.divide(bd2);
注意其中的divide方法如果出现循环结果会抛出异常,需要传入一个RouningMode对象,这里我们不详细解释
java.util.Date
Date类主要用于表示时间,精度为毫秒
Date类在JDK1.1之前可以将时间解释为年月日,以及格式化和解析日期字符串。
但是现在年月日和时间字段转化的方法已经放在Calendar类中,格式化和解析方法在DateFormat类中
Date类中现在还没过时的方法主要有:
构造方法Date()和Date(long data)分别用于获取当前的时间,和传入长整型毫秒数并计算该时间对应的时刻
Date date = new Date();
System.out.println(date);
System.out.println(date.toString());
Date类已经重写了toString方法,所以输出结果是一样的,都会输出该对象被创建的时间;而如果构造时传入一个时间,就会指定该对象代表的时间
getTime(),用于返回当前的时间,单位为毫秒(用于1970年1月1日GMT0点0分到现在的毫秒数表示)
java.text.DateFormat
DateFormat主要用于转化Date类的时间为年月日时分秒格式,DateFormat是抽象的,无法直接使用,通常通过其子类SimpleDateFormat使用
Date date = new Date();
System.out.println(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String a = simpleDateFormat.format(date);
System.out.println(a);
运行结果:
Sun Dec 27 13:57:44 CST 2020
2020年12月27日 13:57:44
Process finished with exit code 0
其中的时间为电脑当前显示的时间,并不是联网获取的,传入的参数为你希望显示的格式,yyyy表示四位数的年,MM,dd,HH,mm,ss分别为两位数的月日时分秒,毫秒则用大写的S表示
你也可以通过parse方法传入一个你定义格式的时间,比如我们之间定义的格式
yyyy年MM月dd日 HH:mm:ss
所以用
simpleDateFormat.parse(“2020年12月27日 13:57:44”)
就可以得到传入时间对应的毫秒数
java.util.Calendar
Calendar类也是抽象的类,但是它提供了解释时间的方法,使用Calendar类可以直接调用其中的静态方法
Calendar calendar = Calendar.getInstance();
// 获取现在的时刻
calendar.set(Calendar.YEAR, 2011);
// 设置其中的年为2011
calendar.add(Calendar.YEAR, 1);
// 使其中的年加一
int year = calendar.get(Calendar.YEAR);// 获取年
int day = calendar.get(Calendar.DAY_OF_MONTH);// 获取今天是本月的第几天
calendar.getActualMaximum(Calendar.DAY_OF_MONTH);// 获取此月总共有多少天
其中有很多的get方法,分别对应了不同的变量,使用十分方便;且在add方法中,如果超出值域会自动进位(比如11月+2得到1月,且年份+1,但是0月表示的是1月,所以最大只有12;而星期则是星期天为1,星期六为7)
同样也能用
Date time = calendar.getTime();
获取Date对象
java.lang.System
System中包含许多常用的静态方法
最常用的莫过于System.out.println,这一个陪同我们从Hello World到现在的方法
System.exit也是一个实用的方法,可以直接结束当前进程,注意我们之前所有正常运行完的进程最后都会跟一句
Process finished with exit code 0
而如果用了System.exit(-1);
最后的0就会变为-1
System.currentTimeMillis();也可以得到当前的时间
arraycopy方法也和之前的Arrays方法一样,能够对原数组进行扩容