JAVA基础知识复习(一)-正文

本文最后更新于:December 3, 2021 pm

JAVA基础知识简单复习一。

目录

1.基本框架

1
2
3
4
5
6
7
//创建文件 HelloWorld.java(文件名需与类名一致)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
//注:String args[] 与 String[] args 都可以执行,但推荐使用 String[] args,这样可以避免歧义和误读。

1.1程序编译和运行

在java环境文件夹目录下打开cmd(或者打开cmd后cd到java的环境文件夹中也行)。输入命令:

1
2
javac HelloWorld.java //编译
java HelloWorld //运行。需要注意的是java命令后面不需要加.class

2.java三大特性

1
2
3
4
5
封装:封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问优点:减少耦合,代码重用,减轻维护
继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:指允许不同类的对象对同一消息做出响应。//只适用于方法,而不适用于属性。
//多态性
//针对方法:编译看左,运行看右。针对属性:编译和运行都看左。

多态一般分为编译时多态和运行时多态,编译时主要指方法的重载;运行时主要指程序中定义的对象引用所指向的具体类型在运行期间才确定

运行时多态有三个条件:继承、覆盖和重写、向上转型(父类引用指向子类对象)


3.java六大原则

1
2
3
4
5
6
1.开闭原则:对扩展开发放,对修改关闭,要求在添加新功能时不需要修改代码,符合开闭原则最典型的设计模式是装饰者模式
2.单一职责原则:一个类只负责一件事,尽量使用合成/聚合的方式,而不是使用继承。
3.里式替换原则 :任何基类可以出现的地方,子类一定可以出现。
4.依赖倒转原则:依赖于抽象而不依赖于具体
5.接口隔离原则:使用多个隔离的接口,比使用单个接口要好 ,不应该强迫客户依赖于它们不用的方法。
6.迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。

4.JAVA关键字

类别 关键字 说明
访问控制 private 私有的
protected 受保护的
public 公共的
default 默认
类、方法和变量修饰符 abstract 声明抽象
class
extends 扩充,继承
final 最终值,不可改变的
implements 实现(接口)
interface 接口
native 本地,原生方法(非 Java 实现)
new 新,创建
static 静态
strictfp 严格,精准
synchronized 线程,同步
transient 短暂
volatile 易失
程序控制语句 break 跳出循环
case 定义一个值以供 switch 选择
continue 继续
default 默认
do 运行
else 否则
for 循环
if 如果
instanceof 实例
return 返回
switch 根据值选择执行
while 循环
错误处理 assert 断言表达式是否为真
catch 捕捉异常
finally 有没有异常都执行
throw 抛出一个异常对象
throws 声明一个异常可能被抛出
try 捕获异常
包相关 import 引入
package
基本类型 boolean 布尔型
byte 字节型
char 字符型
double 双精度浮点
float 单精度浮点
int 整型
long 长整型
short 短整型
变量引用 super 父类,超类
this 本类
void 无返回值
保留关键字 goto 是关键字,但不能使用
const 是关键字,但不能使用
null

5.java注释

支持单行以及多行注释。

1
2
3
4
5
6
// 这是单行注释的示例
/* 这个也是单行注释的示例 */
/* 这是第一个Java程序
* 它将输出 Hello World
* 这是一个多行注释的示例
*/

6.方法重载和重写的区别

1
2
3
4
重写:发生在继承类中,方法名和参数列表相同,重写有以下三个限制:
1.子类方法的访问权限必须大于等于父类方法。
2.子类方法的返回类型必须是父类方法返回类型或为其子类型。如父类为void,则子类也只能为void
3.子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。即子类抛出的异常类型不大于父类的异常类型。
1
重载:发生在同一个类中,方法名相同,参数列表不同(个数、类型、顺序),与权限修饰、返回值类型、抛出异常无关。

注意:构造器是不可以被重写的,但是能重载。
构造方法的特性:

1
2
3
1.名字与类名相同。
2.没有返回值,但不能用void声明构造函数。
3.生成类的对象时自动执行,无需调用。

7.常用修饰符特性

Java中有4中访问权限的修饰符:private、default((默认一般省略)、public、protected。一般用于对类或类中的成员(字段以及方法)加上访问修饰符。
权限的主要作用范围:同一个类中、同一个包下、父子类、不同的包
可被修饰对象:类和成员变量;类可见表示其它类可以用这个类创建实例对象;成员可见表示其它类可以用这个类的实例对象访问到该成员。
4种修饰符的权限范围:

  • private:指”私有的”。被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问
  • default:即不加任何访问修饰符,通常称为“默认访问权限“或者“包访问权限”。该模式下,只允许在同一个包中进行访问。
  • protected: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
  • public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问。
修饰符 同类 同包 子类 不同包非子类
private × × ×
default × ×
protected ×
public

很容易看出,权限范围从小到大依次为:private < default < protected < public

final和static

final


在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)

  • 修饰变量:表示常量,对于基本类型,final 使数值不变;对于引用类型,final 使引用地址不变,但对象本身的属性是可以被修改的。
  • 修饰方法:不能被子类的方法重写,但可以被继承,不能修饰构造方法。。
  • 修饰类 :该不能被继承,没有子类,final类中的方法默认是final的。Java中的String类就是一个final类

static


在Java语言中,static 可以用来修饰成员变量和成员方法,当然也可以是静态代码块

  • 静态变量:又称为类变量,该类的所有实例都共享本类的静态变量,且在内存中只存在一份
  • 静态方法:在类加载的时候就存在了,它不依赖于任何实例,只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字(此时可能没有实例)。
  • 静态语句块:在类初始化时运行一次。
  • 静态内部类:非静态内部类依赖于外部类的实例,而静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法。

需要注意的是:

1
2
1.静态变量,静态方法可以通过类名直接访问
2.初始化顺序:静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。(此处不演示,在类初始化篇章中演示)

由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。

成员变量、静态变量、局部变量的区别

从生命周期比较:

  • 静态变量可以被对象调用,也可以被类名调用。以static关键字申明的变量,其独立在对象之外,有许多对象共享的变量。在对象产生之前产生,存在于方法区静态区中。

  • 成员变量只能被对象调用。随着对象创建而存在,随对象销毁而销毁。存在于堆栈内存中

  • 局部变量在方法或语句块中申明的变量,生命周期只在定义的{}之中,不能跨方法或语句块使用。

从访问权限比较:

  • 静态变量称为对象的共享数据,成员变量可以称为对象的特有数据,局部变量为方法所有

  • 成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

abstract 和 interface

1.abstract

在 Java 中 abstract 即抽象,一般使用 abstract 关键字修饰的类或方法。
修饰的类时,一定有构造器(构造函数),便于子类实例化时调用。

1.不能被实例化,需要继承抽象类后才能实例化其子类。
2.访问权限可以使用public、private、protected,其表达形式为:(public)abstract class 类名{}
3.抽象类不能使用final关键字修饰,因为final修饰的类是无法被继承
4.可以定义构造方法、静态方法、普通方法;非抽象的普通成员变量、静态成员变量

修饰的方法时,只需要声明方法,不需要写方法体(大括号也不写)。

1.含有该抽象方法的类必须定义为抽象类,但抽象类可以没有抽象方法。
2.访问权限可以使用public、default、protected,不能为private,因为抽象方法必须被子类实现(覆写),而private权限对于子类来 说是不能访问的,所以就会产生矛盾,
3.不能用static修饰,因为没有主体
4.若子类没有重写父类中的所有抽象方法,则此子类必须也是一个抽象类,用abstract修饰;否则必须全部重写父类中的抽象类方法。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract  class  MyAbstract {
public String name="小米";
private static int price= 1800;

MyAbstract(String name){
this.name = name;
}

public void test() {
System.out.println(name);
}
public static void fun() {
System.out.println(price);
}
public abstract void print();//权限不能为 private //抽象方法
}

abstract使用注意点:

1.abstract 不能用来修饰 属性、构造器等结构。
2.abstract 不能用来修饰 私有方法、静态方法、final的方法、final的类。

2.interface

在 Java中 interface 即接口,是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,Java 8 开始,接口也可以拥有default的方法实现,是因为不支持默认方法的接口的维护成本太高。

1.接口的方法访问权限只能为 public ,Java 8可以为default,但是必须有方法体
2.接口的方法默认public abstract 也可以由 static 修饰
3.接口的方法可以定义为 public static ,但是必须有方法体,且只能有接口类名调用
4.成员变量默认为public staic final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface  MyInterface {
int price = 1800;

void outName();

default void print() {
System.out.println("MyInterface print: default Method");
}

public static void price() {
System.out.println("MyInterface price="+price);
}
}
public class MyInterfaceImpl implements MyInterface {
@Override
public void outName() {
System.out.println("I'm a MyInterfaceImpl");
}
public static void main(String[] args) {
MyInterface my = new MyInterfaceImpl();
my.outName();
my.print();
// MyInterfaceImpl.print();// 实现类类名调用时, 提示编译错误
MyInterface.price();
}
}
3.abstract 和 interface 的区别

从定义分析

  • 抽象类和接口都不能直接实例化;抽象方法必须由子类来进行重写

  • 抽象类单继承,接口多实现

  • 抽象类可有构造方法,普通成员变量,非抽象的普通方法,静态方法

  • 抽象类的抽象方法访问权限可以为:public、protected 、default

  • 接口中变量类型默认public staic final,

  • 接口中普通方法默认public abstract,没有具体实现

  • jdk1.8 中接口可有静态方法和default(有方法体)方法

从应用场合分析

  • 接口:需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
  • 抽象类:1、在既需要统一的接口,又需要实例变量或缺省的方法的情况下就可以使用它;2、定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口

私有访问修饰符-private

私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。

受保护的修饰符-protected

  • 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
  • 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
    protected 可以修饰数据成员,构造方法,方法成员,不能修饰类(内部类除外)。接口及接口的成员变量和成员方法不能声明为 protected。
    如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。

    访问控制和继承

    注意以下方法继承的规则:
    • 父类中声明为 public 的方法在子类中也必须为 public。
    • 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
    • 父类中声明为 private 的方法,不能够被继承。

非访问修饰符

1.static 修饰符,用来修饰类方法和类变量。
2.final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
3.abstract 修饰符,用来创建抽象类和抽象方法。
4.synchronized 和 volatile 修饰符,主要用于线程的编程。

1.static 修饰符

可以用来修饰:属性、方法、代码块、内部类。

  • 静态变量:
    static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
  • 静态方法:
    static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。

使用 类.方法 或 对象.方法:

静态方法 非静态方法
YES NO
对象 YES YES

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class NewJavaTest {
public static void main(String[] args) {
p it = new p();
p.eat();
// p.sleep(); //不能通过类访问非静态方法
it.eat();
it.sleep();
}

}
class p{
int age;
String s;
public static void eat(){
System.out.println("吃完了");
}
public void sleep(){
System.out.println("睡觉了");
}
}
//输出
吃完了
吃完了
睡觉了

静态方法中:只能调用静态方法或属性。在静态方法内不能使用 this、super关键字。
非静态方法中:既可以调用非静态方法或属性,也可以调用静态的方法或属性。

2.final修饰符

可以修饰:类、方法、变量。

final 类不能被继承,没有类能够继承 final 类的任何特性。

父类中的 final 方法可以被子类继承,但是不能被子类重写。
声明 final 方法的主要目的是防止该方法的内容被修改。

变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。
final 修饰符通常和 static 修饰符一起使用来创建类常量。初始化时,可以在代码块中初始化、构造器中初始化。
例如:

1
2
3
4
5
6
7
8
9
final int w = 0;
final int l;
final int r;
{
l=1;
}
public test(){
r=2;
}

final 也可以在形参中修饰,但被修饰的值在函数中不能改变,只能调用。

1
2
3
4
public void test(int n){
//n=20; 不可以改变值
System.out.println(n);//可以正常输出
}
3.abstract 修饰符

抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
抽象类可以包含抽象方法和非抽象方法。

抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。
抽象方法不能被声明成 final 和 static。
任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
抽象方法的声明以分号结尾,例如:public abstract sample();。

4.synchronized 修饰符

synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。

5.transient 修饰符

序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

6.volatile 修饰符

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是 null。


8.数据类型

Java 语言提供了八种基本类型:六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。并且还提供了其对应的引用类型。

char

在 Java 中是用 unicode来表示字符,所以 2 个字节来表示一个字符; 一个数字或英文或汉字都是一个字符,只不过数字和英文时,存储的2个字节的第一个字节都为0,就是浪费了点空间。存汉字就占满了2个字节。

boolean

在 Java 基本类型中只有两个状态,true、false,理论上只占一个字节,但是实际如下:

  • 单个的boolean类型变量在编译的时候是使用的 int 类型,即 boolean a = true 时,这个a在 JVM 中占用 4 个字节,即32位;
  • boolean类型的数组时,在编译时是作为byte array来编译的。所以,boolean数组里的每一个元件占用一个字节;即 boolean[] b = new boolean[10] 的数组时,每一个boolean在 JVM中占一个字节;

注意: float 和 double 都不能表示精确的值,所以一般不能用在计算货币,要想精度不失效,可以使用 BigDecimal

引用类型

在 Java 中引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。

  • 对象、数组都是引用数据类型;
  • 所有引用类型的默认值都是null;
  • 一个引用变量可以用来引用任何与之兼容的类型
    我们的基本类型都有对应的引用类型,且基本类型与其对应的引用类型之间的赋值使用自动装箱与拆箱完成

    自动装箱和拆箱

也可见 《JAVA基础知识复习(八)-包装类、装箱拆箱》博客内容。

自动拆箱:故名思议就是将对象重新转化为基本数据类型;是享元模式(flyweight)

1
2
Integer num = 10;	//装箱
int num1 = num; //拆箱

基本数据类型和引用数据类型区别

  • 基本数据类型在被创建时,数值直接存储在栈上。
  • 引用数据类型在被创建时,对象的具体信息都存储在堆内存上,对象的引用地址存储在栈上

String类型

String 不可变,String 类型 是一个final修饰的类型。因此它不可被继承。在 Java 8 中,String 内部使用 char 数组存储数据。

1
2
3
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
}

value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
String 不可变的好处

  • 可以缓存 hash 值:因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
  • String Pool 的需要:如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
  • 安全性:String 不可变性天生具备线程安全,可以在多个线程中安全地使用。String 经常作为参数,String 不可变性可以保证参数不可变。

String赋值

1
2
3
String s1 = "bbb";
String s2 = "bbb";
System.out.println(s5 == s6); // true

如果是采用 “bbb” 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。而不是象new一样放在压缩堆中;当声明这样的一个字符串后,JVM会在常量池中先查找有没有一个值为”bbb”的对象,

  • 如果有:就会把它赋给当前引用。即原来那个引用和现在这个引用指点向了同一对象,
  • 如果没有:则在常量池中新创建一个”bbb”,
    下一次如果有String s2 = “bbb”;又会将s2指向”abcd”这个对象;即以这形式声明的字符串,只要值相等,任何多个引用都指向同一对象.

而String s = new String(“abcd”);和其它任何对象一样,每调用一次就产生一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//代码1  
String sa = "ab";
String sb = "cd";
String sab=sa+sb;
String s="abcd";
System.out.println(sab==s); // false
//当执行sa+sb时,JVM首先会在堆中创建一个StringBuilder类,将刚生成的String对象的堆地址存放在局部变量sab中
//局部变量 s 存储的是常量池中"abcd"所对应的拘留字符串对象的地址

//代码2
String sc="ab"+"cd";
String sd="abcd";
System.out.println(sc==sd); //true
//"ab"+"cd"会直接在编译期就合并成常量"abcd", 因此相同字面值常量"abcd"所对应的是同一个拘留字符串对象,自然地址也就相同。

扩展:

1
new String("abc")

这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。

  • “abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个字符串字面量;
  • 而使用 new 的方式会在堆中创建一个字符串对象。

String、StringBuffer、 StringBuilder的区别

  • 从运行速度上说,StringBuilder>StringBuffer>String,因为String是不可变的对象
  • String:是字符串常量(由final修饰),StringBuffer和StringBuilder 是字符串变量
  • StringBuffer:有同步锁,但效率低,适用于多线程下字符缓冲区进行大量操作。
  • StringBuilder:效率高,线程不安全,适用于单线程下的字符缓冲区进行大量操作的情况;

StringBuffer 和 StringBuilder 能大量操作字符的原理
在append是后,采用了Arrays.copyOf() 进行了数组复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}

// AbstractStringBuilder 类
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); // 采用复制方式增加数组长度
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}

String Pool

  • 字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定
  • String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中
    1
    2
    3
    4
    5
    6
    String s1 = new String("aaa");
    String s2 = new String("aaa");
    System.out.println(s1 == s2); // false
    String s3 = s1.intern();
    String s4 = s2.intern();
    System.out.println(s3 == s4); // true
    String#intern 方法:intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中返回这个新字符串的引用,若存在(使用 equals() 方法进行确定)那么就会返回 String Pool 中字符串的引用;

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

String 常用方法

1
2
3
4
5
6
7
8
9
public boolean endsWith(String suffix) //测试此字符串是否以指定的后缀结束
public boolean startsWith(String prefix)
public char charAt(int index);//返回指定索引处的 char 值
public int indexOf(String str)
public int lastIndexOf(int ch)
public String[] split(String regex)
public String substring(int beginIndex)
public String replace(char oldChar,char newChar)
public int length()

Object

在 Java 中 Object 是所有的祖类。

常用方法

1
2
3
4
5
6
7
8
9
10
11
public native int hashCode()
public boolean equals(Object obj)
protected native Object clone() throws CloneNotSupportedException
public String toString()
public final native Class<?> getClass()
protected void finalize() throws Throwable {}
public final native void notify()
public final native void notifyAll()
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException

equals()

  • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
  • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。

    “==”和equals的区别

    ==: 用来判断两个对象的内存地址是否相同(比较的是变量(栈)内存中存放的对象的(堆)内存地址,)。比较的是真正意义上的指针操作。

equals:用来比较的是两个对象的内容是否相等。

例如:

1
2
3
4
5
6
7
8
9
10
String s1 = new String("ab"); // s1 为一个引用
String s2 = new String("ab"); // s2 为另一个引用,对象的内容一样
String s3 = "ab"; // 放在常量池中
String s4 = "ab"; // 从常量池中查找
System.out.println(s1 == s2); // false
System.out.println(s3 == s4); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s2)); // true
System.out.println(s3.equals(s4)); // true
System.out.println(s1.equals(s3)); // true

对equals重新需要注意五点:

1 自反性:对任意引用值X,x.equals(x)的返回值一定为true;
2 对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;
3 传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true ;
4 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变;
5 非空性:任何非空的引用值X,x.equals(null)的返回值一定为false 。

注意
自定义的类用equals比较时,仍然用的 == 在比较。因为自定义类默认继承的Object类中的equals方法,而Object类中的equals方法用的是 == 在比较。Object类中的equals方法代码如下:

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

可以看见,用的是 == 来比较。所以,也是比较的地址值。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NewJavaTest {
public static void main(String[] args) {
Man p = new Man(12,"abcdef"); //这里的Man为自定义类
Man p1= new Man(12,"abcdef");
System.out.println(p.equals(p1));
String s1 = new String("qwer");
String s2 = new String("qwer");
System.out.println(s1.equals(s2));
}

}
//输出
false
true

String 类中的equals 方法代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
//注释为作者自加,并不是String类中所有的
public boolean equals(Object anObject) {
if (this == anObject) { //首先比较两个的地址,如果地址都相同,那么内容一定是相同的
return true;
}
if (anObject instanceof String) { //看两个是不是String类或String类的父类之一,不一定都一样。可以自行建两个类测试。是其之一才比较,不是就不需要比较了
String aString = (String)anObject; //把两个都转换成相同的String类
if (!COMPACT_STRINGS || this.coder == aString.coder) {
return StringLatin1.equals(value, aString.value);
}
}
return false;
}

要想实现自定义类的 equals 方法比较值,而不是比较地址,可以重写 equals 方法。具体写法可以参考 Object 类和 String 类的equals 方法。

hashCode()

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。

所以:在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。

hashCode()与equals()

1.如果两个对象相等,则hashcode一定也是相同的
2.两个对象相等,对两个对象分别调用equals方法都返回true
3.两个对象有相同的hashcode值,它们也不一定是相等的
4.因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
5.hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

clone()

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。

克隆(clone方法)为浅拷贝
1.浅拷贝:对基本数据类型进行值拷贝,对引用数据类型的引用地址进行拷贝,拷贝对象和原始对象的引用类型引用同一个对象

2.深拷贝: 对基本数据类型进行值拷贝,对引用数据类型的内容进行拷贝,拷贝对象和原始对象的引用类型引用不同对象。

深拷贝实现:

  • 序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象,无非就是序列化的规则需要我们自己来写。
  • 实现Clonable接口,覆盖并重写clone(),除了调用父类中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来。如果只是用Object中默认的clone方法,是浅拷贝的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Test implements Cloneable {
    private int[] arr = {1,2,3,4};
    @Override
    protected Test clone() throws CloneNotSupportedException {
    Test newBody = (Test) super.clone();
    newBody.arr = arr.clone(); // 深拷贝实现
    return newBody;
    }

    }
    开发中常用的对象拷贝工具:
    例如DozerMapper、Apache BeanUtils、Spring、Jodd BeanUtils、甚至是Cglib 都提供了这样的功能

选择Cglib的 BeanCopier 进行Bean拷贝的理由是,其性能要比 **Spring的BeanUtils Apache的BeanUtils **和 PropertyUtils 要好很多,尤其是数据量比较大的情况下

Cglib 的beans 包 操作:

  • BeanCopier:用于两个bean之间,同名属性间的拷贝。
  • BulkBean:用于两个bean之间,自定义get&set方法间的拷贝。
  • BeanMap:针对POJO Bean与Map对象间的拷贝。
  • BeanGenerator:根据Map<String,Class>properties的属性定义,动态生成POJO Bean类。

9.this关键字的使用

在一个类中,用来区分形参和类属性(在形参和类属性同名时)。不同名时可以不使用。加上 this 表示是当前对象或者是当前正在创建的对象。可以使用 “this.属性” 或者 “this.方法” 。
使用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class bank {
String name;
int age;
public bank(){

}
public bank(String name,int age){
this.name = name ;
this.age = age ;
}
public void setString(String name){
this.name = name ;
}
public void setAge(int a){
age = a ;
}
}

9.1 this调用构造函数

在一个类的构造函数中,可以再去调用此类的其他构造函数。
用法:this(参数列表);

使用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class bank {
String name;
int age;
public bank(){
System.out.println("A");
}
public bank(String name,int age){
this();
System.out.println("B");
}
public bank(int age){
this("sdf",23);
System.out.println("C");
}
}

第二个构造函数中调用了第一个构造函数;第三个构造函数调用了第二个构造函数。
注意:

  1. 构造函数中不能再调用自己。
  2. 构造函数之间的调用不能形成环(死循环)。
  3. 一个类中如果有 n 个构造函数,则最多有 n-1 个构造函数中可以使用 this(参数列表) 去调用其他的构造函数。
  4. 一个构造函数中的调用声明必须在当前构造函数的首行。
  5. 一个构造函数中只能使用一次 this(参数列表) ,即只能调用一次其他构造函数。(否则就违法了第4条)

本文作者: 墨水记忆
本文链接: https://tothefor.com/DragonOne/1509981974.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!