面向对象编程之Java核心类
廖雪峰java教程学习笔记
本博客是基于廖雪峰java教程写的学习笔记
文章目录
前言
本人计算机小白一枚,写博客的原因一是为了让自己能够更好地理解知识点,加强记忆,二是为了让有需要的同学康康,三是为了能够结交到有相同兴趣的小伙伴,大家一起加油!!!然后本博客只是对自己在教程中不熟悉的地方做一个记录,所以并不全面。
提示:以下是本篇文章正文内容
一、字符串和编码
1:String
在Java中,String是一个引用类型
,它本身也是一个class。但是,Java编译器对String有特殊处理,即直接用“...”
来表示一个字符串。
Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的private final char[]
字段,以及没有任何修改char[]
的方法实现的。
2:字符串比较
当我们想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()
方法而不能用==。
要忽略大小写比较,使用equalsIgnoreCase()方法。
3:编码
Java使用Unicode
编码表示String和char。
二、StringBuilder
为了能够高效拼接字符串,Java标准库提供了StringBuilder
,它是一个可变对象
,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象。
要想使用StringBuilder,需要先import java.lang.StringBuilder;
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(',');
sb.append(i);
}
String s = sb.toString();
StringBuilder还可以进行链式操作:
public class Main {
public static void main(String[] args) {
var sb = new StringBuilder(1024);
sb.append("Mr ")
.append("Bob")
.append("!")
.insert(0, "Hello, ");
System.out.println(sb.toString());
}
}
可以进行链式操作的关键是定义的append()方法会返回this,这样就可以不断调用自身的其他方法。
三、StringJoiner
用分隔符拼接数组的需求很常见,所以Java标准库还提供了一个StringJoiner
来做这件事情。
要想使用StringJoiner,需要先import java.util.StringJoiner
public class Main {
public static void main(String[] args) {
String[] names = {"Bob", "Alice", "Grace"};
var sj = new StringJoiner(", ", "Hello ", "!");
for (String name : names) {
sj.add(name);
}
System.out.println(sj.toString());
}
}
String还提供了一个静态方法join(),这个方法在内部使用了StringJoiner来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()更方便。
四、包装类型
想要把int基本类型变成一个引用类型,我们可以定义一个Integer类,它只包含一个实例字段int,这样,Integer类就可以视为int的包装类
(Wrapper Class):
public class Integer {
private int value;
public Integer(int value) {
this.value = value;
}
public int intValue() {
return this.value;
}
}
定义好了Integer类,就可以把int和integer相互转换:
Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();
实际上,因为包装类型非常有用,Java核心库为每种基本类型都提供了对应的包装类型:
基本类型 | 对应的引用类型 |
---|---|
boolean | java.lang.Boolean |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
char | java.lang.Character |
1:不变类
所有的包装类型都是不变类。我们查看Integer的源码可知,它的核心代码如下:
public final class Integer {
private final int value;
}
因此,一旦创建了Integer对象,该对象就是不变的。
对两个Integer实例进行比较要特别注意:
绝对不能用==比较,因为Integer是引用类型,
必须使用equals()比较
。
因为Integer.valueOf()可能始终返回同一个Integer实例,因此,在我们自己创建Integer的时候,以下两种方法:
方法1:Integer n = new Integer(100);
方法2:Integer n = Integer.valueOf(100);
方法2更好,因为方法1总是创建新的Integer实例,方法2把内部优化留给Integer的实现者去做,即使在当前版本没有优化,也有可能在下一个版本进行优化。
我们把能创建“新”对象的静态方法称为静态工厂方法
。Integer.valueOf()就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。
2:进制转换
Integer类本身还提供了大量方法,例如,最常用的静态方法parseInt()
可以把字符串解析成一个整数。
int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析
Integer还可以把整数格式化为指定进制的字符串.
public class Main {
public static void main(String[] args) {
System.out.println(Integer.toString(100)); // "100",表示为10进制
System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
System.out.println(Integer.toHexString(100)); // "64",表示为16进制
System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制
}
}
Java的包装类型还定义了一些有用的静态变量
。
// int可表示的最大/最小值:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648
最后,所有的整数和浮点数的包装类型都继承自Number,因此,可以非常方便地直接通过包装类型获取各种基本类型:
// 向上转型为Number:
Number num = new Integer(999);
// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();
五、JavaBean
在Java中与很多class的定义都符合这样的规范:
1:若干private实例字段
2:通过public方法来读写实例字段
如果读写方法符合这种命名规范,那么这种class被称为JavaBean。
枚举JavaBean属性
使用Java核心库提供的Introspector:
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) {
thsi.age = age;
}
}
JavaBean是一种符合命名规范的class,它通过getter和setter来定义属性。
可以使用Introspector.getBeanInfo()可以获取属性列表。
六、枚举类
为了让编译器能够自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类:
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
if(day == Weekday.SAT || day == Weekday.SUN) {
System.out.println("Work at home!");
}
else {
System.out.println("Work at office!");
}
}
}
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
enum的比较
使用enum定义的枚举类是一种引用类型,前面我们说到,引用类型的比较,要使用equals()方法,如果使用==比较,它比较的是两个引用类型的变量是否是同一个对象。因此,引用类型比较,要始终使用equals()方法,但enum类型可以例外。
这是因为enum类型的每个常量JVM中只有一个唯一实例,所以可以直接用==比较。
enum类型
enum定义的类型就是class,但是它有以下几个特点:
1:定义的enum类型总是继承自java.lang.Enum,且无法被继承;
2:只能定义出enum的实例,而无法通过new操作符创建enum的实例;
3:定义的每个实例都是引用类型的唯一实例;
4:可以将enum类型用于switch语句。
例如,我们定义一个Color枚举类:
public enum Color {
RED,GREEN,BLUE;
}
编译器编译出的class大概是这样:
public final class Color extends Enum {//继承自Enum,标记为final class
//每个实例均为全局唯一:
public static final Color RED = new Color();
public static final Color GREEN = new Color();
public static final Color BLUE = new Color();
//private构造方法,确保外部无法调用new操作符
private Color() {}
}
因为enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法:
name()
返回常量名
ordinal()
返回定义的常量的顺序,从0开始计数。
改变枚举常量定义的顺序就会导致oridinal()返回值发生改变。
为了让我们编写出的代码更加健壮,我们可以定义private的构造方法,并且,给每个枚举常量添加字段:
enum Weekday {
MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);
public final int dayValue;
private Weekday(int dayValue) {
this.dayValue = dayValue;
}
}
这样就无须担心顺序的变化,新增枚举常量时,也需要指定一个int值。
注意:枚举类的字段也可以是非final类型,既可以在运行期修改,但是不推荐这样做!
默认情况下,对枚举常量调用toString()会返回和name()一样的字符串,但是,toString()可以被覆写,而name()则不行。
注意:判断枚举变量的名字,要始终使用name()方法,绝不能调用toString()!
switch
枚举类可以应用在switch语句中,因为枚举类天生具有类型信息和有限个枚举常量,所以比int、String类型更适合用在switch语句中。
七、记录类
不变类具有以下特点:
1:定义class时使用final,无法派生子类;
2:每个字段使用final,保证创建实例后无法修改任何字段。
示例:
public final class Point {
private final int x;
private final int y;
public Point(int x,int y) {
this.x = x;
this.y = y;
}
public int x() {return this.x;}
public int y() {return this.y;}
}
为了不变类的比较,需要覆写equals()和hashCode()方法,这样才能在集合类中使用,这个知识点我们暂不详谈。
为了让这种简单而繁琐的代码写起来更加容易,从Java 14开始,引入了新的Record类,定义Record
类时,使用关键字record
如下所示
public record Point(int x,int y) {}
将上述定义改写为class,不仅包括我们自己定义的内容,编译器还自动为我们创建了构造方法,和字段同名的方法,以及覆写toString()、equals()和hashCode()方法。
所以,使用record
关键字可以一行写出一个不变类。
和enum
类似,不能直接从Record派生,只能通过record
关键字由编译器实现继承。
构造方法
编译器默认按照record
声明的变量顺序自动创建一个构造方法,并在方法内给字段赋值。但是如果我们需要检查参数,应该怎么办?
示例:
public record Point(int x,int y) {
public Point {
if (x<0 || y<0) {
throw new IllegalArgumentException();
}
}
}
方法public Point {……}被称为Compact Constructor,它的目的是让我们编写检查逻辑,编译器最终生成的构造方法如下:
public record Point(int x,int y) {
public Point {
if (x<0 || y<0) {
throw new IllegalArgumentException();
}
//这是编译器继续生成的赋值代码
this.x = x;
thsi.y = y;
}
...
}
作为record的Point仍然可以添加静态方法,一种常用的静态方法是of()方法,用来创建Point:
public record Point(int x, int y) {
public static Point of() {
return new Point(0, 0);
}
public static Point of(int x, int y) {
return new Point(x, y);
}
}
这样创建Point就很简单:
var z = Point.of();
var p = Point.of(123,456);
八、BigInteger
如果我们使用的整数范围超过了long型整数,这个时候就只能使用软件来模拟一个大整数,java.math.BigInteger
就是用来表示任意大小的整数。其内部使用一个int[]数组来模拟一个非常大的整数。
对BigInteger
做运算,只能使用实例方法,例如,加法运算:
BigInteger i1 = new BigInteger("1234567890");
BigInteger i2 = new BigInteger("12345678901234567890");
BigInteger sum = i1.add(i2); // 12345678902469135780
可以把BigInteger
转换成long
型:
BigInteger i = new BigInteger("123456789000");
System.out.println(i.longValue()); // 123456789000
System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range
使用longValueExact()
方法时,如果超出了long
型的范围,会抛出ArithmeticEception
。
BigInteger
和Integer
、long
一样,也是不可变类,并且也继承自Number类。因为Number定义了转换为基本类型的几个方法:
1、转换为byte
:byteValue()
2、转换为short
:shortValue()
3、转换为int
:intValue()
4、转换为long
:longValue()
5、转换为flota
:floatValue()
6、转换为double
:doubleValue()
因此,通过上述方法,可以把BigInteger
转换成基本类型。
九、BigDecimal
和BigInteger
类似,BigDecimal
可以表示一个任意大小且精度完全准确的浮点数。BigDecimal
用scale()
表示小数位数,例如:
BigDecimal d1 = new BigDecimal("123.45");
BigDecimal d2 = new BigDecimal("123.4500");
BigDecimal d3 = new BigDecimal("1234500");
System.out.println(d1.scale()); // 2,两位小数
System.out.println(d2.scale()); // 4
System.out.println(d3.scale()); // 0
用BigDecimal
的stripTrailingZeros()
方法,可以将一个BigDecimal
格式化为一个相等的,但去掉了末尾0的Bigdecimal
,如果一个BigDecimal
的scale()
返回负数,例如,-2
,表示这个数是个整数,并且末尾有两个0。
可以对一个BigDecimal
设置它的scale
,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("123.456789");
BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567
System.out.println(d2);
System.out.println(d3);
}
}
对BigInteger
做加减乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断:
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入
BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽
还可以对BigDecimal
做除法的同时求余数,可以调用divideAndRemainder()
方法时,返回的数组包含两个BigDecimal
,分别是商和余数,其中商总是整数,余数不会大于除数。
比较BigDecimal
在比较两个BigDecimal
的值是否相等时,要特别注意,使用equals()
方法不但要求两个BigDecimal
的值相等,还要求它们的scale()
相等,所以我们总是使用compareTo() 方法来比较,根据返回的是负数、正数、0,分别表示小于、大于和等于。
如果查看BigDecimal的源码,可以发现,实际上一个BigDecimal是通过一个BigInteger和一个scale来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数。
BigDecimal
也是从Number
继承的,也是不可变对象。
十、常用工具类
Java的核心库提供了大量的现成的类供我们使用。这波我们记录几个常用的工具类。
Math
绝对值—>Math.abs()
最大最小—>Math.max(),Math.min()
a的b次方—>Math.pow(a,b)
平方根—>Math.sqrt()
e的x次方—>Math.exp()
以e为底的对数—>Math.log()
以10为底的对数—>Math.log10()
三角函数—>Math.sin(),Math.cos(),Math.tan(),Math.asin(),Math.acos()
数学常量—>Math.PI,Math.E
生成随机数—>Math.random()
但是随机数的范围是0 <= x < 1
,要生成区间[MIN,MAX)的随机数,可以借助Math.random(),如下:
// 区间在[MIN, MAX)的随机数
public class Main {
public static void main(String[] args) {
double x = Math.random(); // x的范围是[0,1)
double min = 10;
double max = 50;
double y = x * (max - min) + min; // y的范围是[10,50)
long n = (long) y; // n的范围是[10,50)的整数
System.out.println(y);
System.out.println(n);
}
}
Java标准库还提供了一个StrictMath,它提供了和Math几乎一模一样的方法。这两个类的区别在于,由于浮点数计算存在误差,不同的平台(例如x86和ARM)计算的结果可能不一致(指误差不同),因此,StrictMath保证所有平台计算结果都是完全相同的,而Math会尽量针对平台优化计算速度,所以,绝大多数情况下,使用Math就足够了。
Random
Random用来创建伪随机数
。所谓伪随机数,是指只要给定一个初始的种子
,产生的随机数序列是完全一样的。
示例:
import java.util.Random;
Random r = new Random();
r.nextInt(); // 2071575453,每次都不一样
r.nextInt(10); // 5,生成一个[0,10)之间的int
r.nextLong(); // 8811649292570369305,每次都不一样
r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
r.nextDouble(); // 0.3716...生成一个[0,1)之间的double
SecureRandom
有伪随机数
,就有真随机数
。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的
SecureRandom sr = new SecureRandom();
System.out.println(sr.nextInt(100));
需要使用安全随机数的时候,必须使用SecureRandom,绝不能使用Random!
总结
提示:这里对文章进行总结:
以上就是我在学习廖雪峰的Java教程中学习到的相关知识。