面向对象编程之Java核心类

面向对象编程之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核心库为每种基本类型都提供了对应的包装类型

基本类型对应的引用类型
booleanjava.lang.Boolean
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
charjava.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
BigIntegerIntegerlong一样,也是不可变类,并且也继承自Number类。因为Number定义了转换为基本类型的几个方法:
1、转换为bytebyteValue()
2、转换为shortshortValue()
3、转换为intintValue()
4、转换为longlongValue()
5、转换为flotafloatValue()
6、转换为doubledoubleValue()
因此,通过上述方法,可以把BigInteger转换成基本类型。

九、BigDecimal

BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。BigDecimalscale()表示小数位数,例如:

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

BigDecimalstripTrailingZeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的Bigdecimal,如果一个BigDecimalscale()返回负数,例如,-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教程中学习到的相关知识。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlanAbner

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值