匿名对象
格式:
匿名对象只可以调用一次成员 :
1. 调用一次成员变量 : new 类名(实参).成员变量名;
2.调用一次成员方法: new 类名(实参).成员方法名(实参);
匿名对象存在的必要:为了提高内存的使用性能,如果有一个变量(对象),在内存中再也不能使用了,那么这个对象很快就会被JMV标记成垃圾,被回收掉。
内部类
实例内部类
格式:
//外部类
class External{private int a1=1;private int a2=2;private static int a3=3;//实例内部类public class Inside{private int a1=1111;private int a4=4;private static final int a5=5;// 在实例内部类中声明静态常量//实例内部类对象必须在先有外部类对象前提下才能创建//实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束public void way(){//在实例内部类方法中访问同名的成员时,优先访问自己的System.out.println(a1);//如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问System.out.println(External.this.a1);//每个实例内部类都有一个指向其外部类实例的隐式引用。即 外部类名称.this.外部类成员//换句话说,当在 way 中访问外部类的成员时,它实际上是通过 外部类名称.this 来引用外部类的实例//所以外部类中的任何成员都可以在实例内部类方法中直接访问System.out.println(a2);System.out.println(a3);System.out.println(a4);System.out.println(a5);System.out.println("这是实例内部类");}}public void way(){//外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。Inside inside = new Inside();System.out.println(inside.a4);System.out.println("这是外部类");}
}
public class Test {public static void main(String[] args) {//获取实例内部类对象//方法1External external = new External();//创建外部实例External.Inside inside1 = external.new Inside(); // 通过外部类实例创建内部类实例inside1.way();// 调用内部类的方法external.way();//方法2//通过new External() 来创建一个新的外部类实例,并通过 new Inside() 创建对应的内部类实例。External.Inside inside2 = new External().new Inside();// 在一行中创建外部类和内部类实例inside2.way();}
}
静态内部类
静态内部类的特点:
- 静态修饰: 静态内部类使用
static
关键字修饰,意味着它是外部类的一部分,但它的生命周期不依赖于外部类的实例。 - 可以访问外部类的静态成员: 静态内部类可以访问外部类的静态变量和静态方法,但无法直接访问外部类的实例成员变量和方法。
- 实例化方式: 静态内部类不依赖于外部类的实例,可以通过外部类名直接实例化,可以直接通过外部类来访问。
- 可以独立存在: 静态内部类是一个独立的类,可以像普通类一样被实例化和使用。它不会像实例内部类一样必须依赖外部类实例来创建。
静态内部类的使用场景:
- 封装: 当一些功能和外部类紧密相关,但又不需要依赖外部类的实例时,使用静态内部类来封装这些功能。
//外部类
class External{public int a1=1;public static int a2=2;public static void way1(){System.out.println(a2);}//静态内部类:被static修饰的内部类成员static class Static{//可以访问外部类的静态变量public static int a3=3;public int a4=4;public void way(){//System.out.println(a1);//编译失败,因为a1不是静态成员变量System.out.println(a2);//静态内部类可以访问外部类的静态变量和静态方法System.out.println(a3);System.out.println(a4);}}
}
public class Test {public static void main(String[] args) {//创建静态内部类对象时,不需要先创建外部类对象External.Static st=new External.Static();st.way();}
}
局部内部类
局部内部类的特点:
- 定义位置: 局部内部类只能定义在外部类的 方法 或 构造函数 内部。
- 作用域: 仅在外部方法内部有效,方法结束后局部内部类实例将被销毁,不能在外部方法之外被访问或创建
- 访问限制: 局部内部类只能访问方法内的
final
或 有效的 final 局部变量(即那些在方法中没有被修改过的变量),可以访问外部类中的实例变量。 - 不可以使用访问修饰符: 局部内部类不能使用
public
、private
等访问修饰符,因为它的作用范围仅限于方法内。 - 不能使用静态修饰符: 因为局部内部类是方法的一部分,不能定义为静态的。
局部内部类的使用场景:
- 当某些功能只在方法内部需要时,可以使用局部内部类来实现。
- 适用于需要封装逻辑但只在某个方法内使用的类,避免在外部暴露不必要的复杂性。
//外部类
class External{public String a1="外部类中的实例成员变量";public final String a2="外部类中不能被修改的实例成员变量";public void display(){System.out.println("外部类的方法");}//外部类的方法public void meth(){String a3="方法中的局部变量";final String a4 = "方法中的局部常量";//局部内部类//局部内部类不能使用 public、private 等访问修饰符,因为它的作用范围仅限于方法内。class Inner{String a5="局部内部类中的局部变量";public void way(){String a6="局部内部类方法中的局部变量";//局部内部类可以访问外部类中的实例变量System.out.println(External.this.a1);System.out.println(a1);System.out.println(a2);//局部内部类可以访问外部类的方法display();//局部内部类可以访问外部方法中的未被修改过的局部变量System.out.println(a3);System.out.println(a4);System.out.println(a5);System.out.println(a6);}}//局部内部类只在外部方法的作用域内有效,方法结束后将会被销毁,不能在外部方法之外被访问或创建//在meth()方法内部创建局部内部类实例Inner inner = new Inner();inner.way();//调用局部内部类的方法}
}
public class Test {public static void main(String[] args) {External external = new External();external.meth();//调用外部类中的方法,触发局部内部类的创建}
}
匿名内部类
格式:
匿名类的调用:
方法1:可以直接调用匿名类中特有的方法
方法2:使用场景
匿名类通常作为一个参数传递给方法
匿名类的特点:
匿名类本质就是一个子类,匿名类可以作为接口实现或者类继承的匿名子类。匿名类在定义时就会立即实例化,并且可以用于初始化变量。
代码实例:
1. 不使用匿名方法
//创建接口
interface Animal{void run();
}
//创建实现接口的方法
class Dog implements Animal{//方法重写public void run(){System.out.println("跑得快");}
}
public class Test {public static void main(String[] args){running(new Dog());}public static void running(Animal animal){//Animal animal = new Dog;向上转型animal.run();}
}
2. 使用匿名方法
//创建接口
interface Animal{void run();
}
public class Test {public static void main(String[] args){String name ="小狗";//通过匿名类访问局部变量,在JDK8版本之前,必须加final关键字,表示常量,即不能被修改new Animal(){//方法重写public void run(){System.out.println(name+"跑得快");}}.run();}
}
//创建接口
interface Animal{void run();
}
public class Test {public static void main(String[] args){String name ="小狗";//通过匿名类访问局部变量,在JDK8版本之前,必须加final关键字,表示常量,即不能被修改running(new Animal(){//方法重写public void run(){System.out.println(name+"跑得快");}});}public static void running(Animal animal){animal.run();}
}
Object 类
Object
类是所有类的根类,位于 java.lang
包中。这意味着 Java 中的每个类都是 Object
类的子类,直接或间接地继承了 Object
类的属性和方法。
方法签名 | 描述 |
---|---|
public String toString() | 返回该对象的字符串表示。如果子类没有重写该方法,则默认返回对象的类名、 |
public boolean equals(Object obj) | 指示某个其他对象是否与此对象相等。默认行为是仅在两个对象相同(即引用相等)时返回 true 。 |
public int hashCode() | 返回该对象的十进制哈希码值。默认哈希码是对象的内存地址转换成的整数。如果 equals() 方法被重写,则通常也需要重写 hashCode() 方法,以确保相等的对象有相同的哈希码。 |
protected Object clone() | 创建并返回此对象的一个副本。默认实现抛出 CloneNotSupportedException ,因此子类需要重写此方法以支持克隆操作,并实现 Cloneable 接口 |
1. toString 方法
this.getClass():获取一个类的字节码 this.getClass().getName():获取一个类的全限定名(包名+类名) this.hashCode():获取一个对象的哈希码值,是一个十进制数据 Integer.toHexString(十进制数据):把十进制数据转换成十六进制数据
重写toString方法
@Overridepublic String toString() {//Animal{name='',age= };return "Animal{" +"name='" + name + '\'' +", age=" + age +'}';}
结论:
如果一个类没有重写toString方法,那么打印此类的对象是地址值
如果一个类重写了toString方法,那么打印此类的对象打印的是内容
2. equals 方法
"=="
1. 比较基本数据类型:比较数据值是否相等
2. 比较引用数据类型:比较地址是否相等
//Object 中的equals方法public boolean equals(Object obj) {//this : 调用此方法中的对象 a1//object : a2return (this == obj);
}
public class Test {public static void main(String[] args) {Animal a1=new Animal("小狗",6);Animal a2=new Animal("小狗",6);System.out.println(a1.equals(a2));//false}
}
重写equals
@Overridepublic boolean equals(Object o) {//判断地址是否相同,如果地址相同说明内容一定相同//提高代码执行效率if (this == o) return true;//getClass():获取一个类的字节码对象,同一个类的字节码对象是相等的//getClass() != o.getClass():保证比较对象的类型是一致的//o == null :如果a2为null,直接返回false,提高代码的健壮性,不容易报错//为什么不写a1是否为null,因为a1为null,代码在打印时直接报错,不进入equals方法if (o == null || getClass() != o.getClass()) return false;//向下转型,目的是为了调用子类特有的成员Animal animal = (Animal) o;//此equals为String类当中的equals,目的是比较字符串的内容return age == animal.age && Objects.equals(name, animal.name);}
结论:
如果一个类没有重写equals方法,那么比较对象比较的是地址值是否相等
如果一个类重写了equals方法,那么比较对象比较的是内容是否相等
3. hashCode 方法
返回该对象的十进制哈希码值。默认哈希码是对象的内存地址或字符串或数字转换成的整数
自定义类的对象哈希码值默认返回对象的地址
在Object类中
public class Test {public static void main(String[] args) {Animal a1=new Animal("小狗",6);Animal a2=new Animal("小狗",6);System.out.println(a1.hashCode());System.out.println(a2.hashCode());}
}
重写hashCode方法
@Overridepublic int hashCode() {return Objects.hash(name, age);}
结论:
如果一个类没有重写hashCode方法,那么内容相同的对象将返回不同的哈希码值,即存储在不同的位置
如果一个类重写hashCode了方法,那么内容相同的对象将返回相同的哈希码值,即存储在相同的位置
但内容相同的对象在通过equals方法比较时,应该具有相同的哈希码值,所以重写 hashCode()
方法是为了确保内容相同的对象具有相同的哈希码值,这样它们在哈希表中可以被存储在相同的位置,从而提高哈希表的性能和正确性。
4. clone 方法
1. 浅克隆
拷贝出的新对象,与原对象中的数据一模一样(引用类型拷贝的只是地址)
import java.util.Arrays;
import java.util.Objects;//Cloneable是一个标记性接口:证明当前类是可以被克隆的class Animal extends Object implements Cloneable{public String name;public int age;public double[] quantity;public Animal(String name, int age, double[] quantity) {this.name = name;this.age = age;this.quantity = quantity;}@Overridepublic String toString() {return "Animal{" +"name='" + name + '\'' +", age=" + age +", quantity=" + Arrays.toString(quantity) +'}';}//浅拷贝@Overrideprotected Object clone() throws CloneNotSupportedException {//调用Object中的clone方法并返回return super.clone();}
}public class Test {public static void main(String[] args) throws CloneNotSupportedException {Animal a3=new Animal("小狗",5,new double[]{20,30,40});System.out.println(a3);System.out.println(a3.quantity);Animal a4=(Animal)a3.clone();System.out.println(a4);System.out.println(a4.quantity);}
}
2. 深克隆
对象中基本类型的数据直接拷贝
对象中的字符串拷贝的是地址
对象中包含的其他对象,不会拷贝地址,会创建新的对象
public Object clone() throws CloneNotSupportedException {Animal animal = (Animal) super.clone();//把对象中的数组单独克隆一次animal.quantity=animal.quantity.clone();return animal;}
Comparable 接口
使用Arrays.sort
方法对 Animal
对象进行排序,需要元素实现 Comparable
接口或者传递一个 Comparator
实例。
1. 实现 Comparable
接口:在 Animal
类中定义排序规则,让 Animal
类实现 Comparable<Animal>
,并在 compareTo
方法中定义排序规则,如按照 age
升序排序。
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Comparator;class Animal implements Comparable<Animal> {public String name;public int age;public Animal(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Animal{" +"name='" + name + '\'' +", age=" + age +'}';}//重写compareTo方法@Overridepublic int compareTo(Animal o){return this.age-o.age;}
}
public class Test {public static void main(String[] args) {Animal a1=new Animal("大黄" ,5);Animal a2=new Animal("小黑",3);System.out.println(a1.compareTo(a2));//通过compareTo对a1和a2对象的age进行比较Animal[] animals=new Animal[]{new Animal("大黄",7),new Animal("小黑",5),new Animal("小胖",4),new Animal("小白",9)};Arrays.sort(animals);System.out.println(Arrays.toString(animals));}
}
我们发现comparable接口拓展性不强,只能通过age进行比较,对类的侵入性比较强
2. 使用 Comparator
接口:在调用 Arrays.sort
时传入自定义的比较器。
//使用匿名方法Arrays.sort(animals,new Comparator<Animal>(){//重写Comparator接口中的compare方法public int compare(Animal o1,Animal o2){return o1.age-o2.age;//按年龄升序排列}});System.out.println(Arrays.toString(animals));//使用匿名方法Arrays.sort(animals,new Comparator<Animal>(){//重写Comparator接口中的compare方法public int compare(Animal o1,Animal o2){//因为String类实现了Comparable<string>接口//使用可以调用compareTo方法比较两个字符串大小return o1.name.compareTo(o2.name);//按姓名升序排列}});System.out.println(Arrays.toString(animals));
两种方法的区别
-
实现
Comparable
:- 优点:直接在类中定义了排序规则,适合需要多次排序的场景,代码复用性高。
- 缺点:只能实现一种排序规则,无法灵活切换。
-
使用
Comparator
:- 优点:可以为不同场景定义不同的排序规则,灵活性更高。
- 缺点:每次排序都需要定义规则,代码复用性较低