Java中Object和內(nèi)部類舉例詳解
前言:
上期內(nèi)容為大家?guī)砹顺橄箢惻c接口的知識(shí)點(diǎn)的學(xué)習(xí),這期內(nèi)容我將為大家?guī)砹薕bject類與內(nèi)部類的兩種類的學(xué)習(xí),這其內(nèi)容是對(duì)于類與對(duì)象的補(bǔ)充。
一、Object類
1.object類初識(shí)
Object類是Java中一個(gè)默認(rèn)提供的類,Java里面內(nèi)置了object類,其余所有的類都是具有繼承關(guān)系的,默認(rèn)繼承Object父類,那么也就是所有的對(duì)象都是可以使用object的引用來接收。
所以也可以發(fā)生了向上轉(zhuǎn)型。
class Person1{}
class Student{}
class Test {
public static void main(String[] args) {
function(new Person1());
function(new Student());
}
public static void function(Object obj) {
System.out.println(obj);
}
}

這個(gè)時(shí)候我們可以看到打印的是對(duì)象所在位置的類型名和哈希值。

通過API文檔,我們可以看到object類中有許多方法,這些方法我們都要全部掌握!
但是我們已經(jīng)學(xué)習(xí)了clone方法了,這節(jié)我們要掌握三種方法,分別是:toString,hashCode,equals方法
hashCode我們后期在哈希表會(huì)再次進(jìn)行詳細(xì)的講解。
2.Object的方法
2.(1).獲取對(duì)象的信息–toString方法
class Person{
public String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Person person1 = new Person("張三",10);
System.out.println(person1);
Person person2 = new Person("張三",10);
System.out.println(person2);
}

這個(gè)時(shí)候我們看到打印的是對(duì)象所在位置的類型的名和哈希值
我們看一下Object類中toString方法的源碼:

獲取的是對(duì)象所在位置的類型的名和哈希值。
如果我們想要打印對(duì)象的成員信息,這個(gè)時(shí)候我們可以重寫這個(gè)toString方法,去獲取對(duì)象成員變量的信息。
class Person{
public String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
Person person1 = new Person("張三",10);
System.out.println(person1);
Person person2 = new Person("張三",10);
System.out.println(person2);
}
既然重寫toString方法,那么就會(huì)發(fā)生了多態(tài)。

2.(2).對(duì)象比較equals方法
在Java中,我們要比較兩者數(shù)據(jù)之間的是否一樣用等于號(hào)來進(jìn)行,比如我們用==來進(jìn)行比較,如果兩者不一樣,則返回假,一樣就返回真。
public static void main(String[] args) {
int a = 10;
int b = 10;
System.out.println(a == b);
}
//這個(gè)時(shí)候我們打印會(huì)的是true
那么我們比較兩個(gè)自定義類型呢?
public static void main(String[] args) {
Person person1 = new Person("張三",10);
System.out.println(person1);
Person person2 = new Person("張三",10);
System.out.println(person2);
System.out.println("=========");
System.out.println(person1 == person2);
}

這里表明了兩個(gè)person不是同一個(gè),為什么會(huì)出現(xiàn)這種情況呢?那么我們此時(shí)來觀察一下他們所處的地址

我們看到了這兩地址也就是哈希值是完全不同的,所以我們可以明白"=="
是比較自定義類型的地址是否是完全相同的。
有一個(gè)方法叫做equals,也是比較兩個(gè)對(duì)象是否相同,下面我們可以看看這個(gè)方法的源碼:

我們會(huì)發(fā)現(xiàn)它返回的是也是地址,比較判斷對(duì)象是否指向了一個(gè)同一個(gè)地址。
所以直接調(diào)用還是會(huì)為false但是我們不想讓它比較的是地址,就像上述代碼一般,兩個(gè)張三的實(shí)例化,就應(yīng)該是同一個(gè)對(duì)象,所以這個(gè)時(shí)候就要涉及到重寫equals方法了。
class Person{
public String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("張三",10);
System.out.println(person1);
Person person2 = new Person("張三",10);
System.out.println(person2);
System.out.println("=========");
System.out.println(person1 == person2);//比較兩者實(shí)例化后是不是一個(gè)person
//結(jié)果是false
//通過直接打印了它們的地址,發(fā)現(xiàn)兩者的地址不同
//所以 時(shí)候比較的就是變量中的值
System.out.println(person1.equals(person2));
}
}
這個(gè)時(shí)候我們重寫equals方法,這個(gè)時(shí)候的結(jié)果就是:true

上述代碼這個(gè)重寫equals方法是IDEA為我們進(jìn)行重寫的,我們可以自己手動(dòng)重寫一個(gè)equals方法
比如:
@Override
public boolean equals(Object obj) {
if (obj == null) {//如果是被比較的對(duì)象是空類型
return false ;
}
if(this == obj) {//兩者指向了一個(gè)對(duì)象的空間,那么獲得的地址是一個(gè),所以兩者是一個(gè)
return true ;
}
// 不是Person類對(duì)象
if (!(obj instanceof Person)) {//判斷person
return false ;
}
Person person = (Person) obj ; // 向下轉(zhuǎn)型,比較屬性值,兩者的屬性是自己定義的,不是object類
return this.name.equals(person.name) && this.age==person.age ;
}
兩者的邏輯是一樣的,都可以實(shí)現(xiàn)equals方法重寫。
結(jié)論:所以通過equals方法的比較,我們要明白:如果是以后自定義的類型,那么一定要記住重寫equals方法
并且在比較對(duì)象中內(nèi)容是否相同的時(shí)候,一定要重寫equals方法。
2.(3).hashCode方法
哈希值:hashcode這個(gè)方法會(huì)幫我們算出一個(gè)地址以16進(jìn)制輸出我們看到對(duì)象那個(gè)的打印的時(shí)候,我們會(huì)發(fā)現(xiàn)打印了哈希值,也就是這個(gè)方法

hashCode方法,那么我們可以進(jìn)行查看一下hashCode的源碼:

native的方法代表著我們是本地方法,用C/C++來進(jìn)行寫的方法,所以我們也看不到的這個(gè)方法中的具體實(shí)現(xiàn)。
比如我們這個(gè)時(shí)候直接打印一下它們的哈希值:
System.out.println(person1.hashCode()); System.out.println(person2.hashCode());

我們可以發(fā)現(xiàn)兩者的哈希值是不同的,但是也代表兩者的地址不同,因?yàn)槲覀冋J(rèn)為兩個(gè)名字相同,年齡相同的對(duì)象,將存儲(chǔ)在同一個(gè)位置,所以這個(gè)時(shí)候我們就需要重寫hashCode()方法了
我們?cè)赑erson類中重寫了hashCode方法:
@Override
public int hashCode() {
return Objects.hash(name, age);
}
這個(gè)時(shí)候再來比較person1和person2是否相同:

我們可以看到結(jié)果是兩者的哈希值是一樣的。
如果我們沒有重寫了hashCode方法,那么就是直接打印的是兩者的哈希值。
如果我們判斷名字和姓名是一樣,那么就不需要重復(fù)在開辟空間了,所以我們這個(gè)時(shí)候可以重寫hashcode
所以我們重寫的hashcode會(huì)發(fā)現(xiàn)某些對(duì)象出現(xiàn)在堆中同一個(gè)位置
后期講到哈希表的時(shí)候便會(huì)知道(現(xiàn)實(shí)邏輯中的)如果兩個(gè)一樣的對(duì)象想放在同一個(gè)位置,
此時(shí)我就可以利用重寫hashcode這個(gè)方法來實(shí)現(xiàn)
結(jié)論:
1、hashcode方法用來確定對(duì)象在內(nèi)存中存儲(chǔ)的位置是否相同
2、事實(shí)上hashCode() 在散列表中才有用,在其它情況下沒用。在散列表中hashCode() 的作用是獲取對(duì)象的
散列碼,進(jìn)而確定該對(duì)象在散列表中的位置。
二、內(nèi)部類
1.內(nèi)部類初識(shí):
內(nèi)部類:
在 Java 中,可以將一個(gè)類定義在另一個(gè)類或者一個(gè)方法的內(nèi)部,前者稱為內(nèi)部類,后者稱為外部類。內(nèi)部類也是封裝的一種體現(xiàn)。
關(guān)于類,我們深知是一個(gè)類只有一個(gè)對(duì)應(yīng)的字節(jié)碼文件,但是如果我們遇到這種內(nèi)部類,會(huì)發(fā)現(xiàn)一個(gè)類中還有一定的類,我們先寫一個(gè)內(nèi)部類,觀察一下:
class OutClass{
class InnerClass{
}
}
通過IDEA打開了本地文件的字節(jié)碼的文件:

我們可以看到它有OutClass和OutClass$InnerClass兩個(gè)字節(jié)碼文件
所以我們就可以知道內(nèi)部類是外部類$內(nèi)部類,當(dāng)然還有一種是外部類$數(shù)字,我們會(huì)在后面講解。
2.內(nèi)部類的分類:
有三種內(nèi)部類:靜態(tài)、實(shí)例、匿名、局部 總共三種內(nèi)部類;
使用頻率:實(shí)例>靜態(tài)>匿名>局部
2.(1).實(shí)例內(nèi)部類
實(shí)例內(nèi)部類就是在普通的類中去定義一個(gè)類,比如我們定義一個(gè)實(shí)例內(nèi)部類:
class OutClass1{
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
class InnerClass{
public int data1 = 11111;
public int data4 = 4;
private int data5 = 5;
public static int data6 = 6;
public void test(){
System.out.println(data1);
System.out.println(data5);
System.out.println("內(nèi)部類的test方法");
}
}
public void test(){
System.out.println("外部類的test方法");
}
}
我們定義一個(gè)實(shí)例內(nèi)部類,這個(gè)時(shí)候我們來執(zhí)行這個(gè)實(shí)例內(nèi)部類
public class Test2 {
public static void main(String[] args) {
OutClass1 outClass1 = new OutClass1();
System.out.println(outClass1.data1);
System.out.println("================");
InnerClass innerClass = new InnerClass();
}
}
這時(shí)候就會(huì)顯示報(bào)錯(cuò):

所以實(shí)例內(nèi)部類不能同正常的類去進(jìn)行實(shí)例化,我們應(yīng)該注意,用外部類訪問內(nèi)部類
所以我們用外部類名去訪問內(nèi)部類:
OutClass1 outClass1 = new OutClass1(); OutClass1.InnerClass innerClass = outClass1.new InnerClass();//我們用對(duì)象名去調(diào)用內(nèi)部類名 innerClass.test(); //上述這種寫法是把outClass的對(duì)象名給其去調(diào)用 //我們也可以直接把對(duì)象引用: OutClass1.InnerClass innerClass2 = new OutClass1().new InnerClass(); innerClass2.test();
當(dāng)內(nèi)部類數(shù)據(jù)成員和外部類數(shù)據(jù)成員是同名的時(shí)候,會(huì)怎么樣?,讓我們來進(jìn)行看一下:

這次執(zhí)行成功了,但是我們會(huì)發(fā)現(xiàn)了外部類與內(nèi)部類均有data1,但是我們發(fā)現(xiàn)打印的是內(nèi)部類中的data1,
這個(gè)時(shí)候就是前面所講的就近原則,Java的編譯器進(jìn)行檢查的時(shí)候,發(fā)現(xiàn)內(nèi)部類的data1與其位置最近,那么優(yōu)先訪問它
那么我們要訪問外部的data1呢?我們可以加上引用,
class InnerClass{
public int data1 = 11111;
public int data4 = 4;
private int data5 = 5;
public static int data6 = 6;
public void test(){
System.out.println(data1);
System.out.println(this.data1);//我們加上了this引用,這個(gè)時(shí)候表明這個(gè)data1是哪一類中的
System.out.println(OutClass1.this.data1);
System.out.println(data5);
System.out.println("內(nèi)部類的test方法");
}
}

這個(gè)this就表明當(dāng)前誰在調(diào)用這個(gè)內(nèi)部類的成員變量,然后我們用外部類的this
那么可能會(huì)有人說,在外部類的方法中去訪問內(nèi)部類的成員,我們可以先來看一下:

//這個(gè)時(shí)候IDEA也就會(huì)自動(dòng)爆紅,顯示出錯(cuò),但是我們也可以來運(yùn)行一下代碼,但是也會(huì)發(fā)現(xiàn)它們會(huì)報(bào)錯(cuò);

因?yàn)檫@個(gè)data4是在內(nèi)部類中,我們沒有內(nèi)部類的實(shí)例化,外部類是無法訪問的其內(nèi)部類中的成員變量的
所以我們需要先進(jìn)行內(nèi)部類的實(shí)例化才可以執(zhí)行這個(gè)代碼:
public void test(){
System.out.println("外部類的test方法");
//訪問內(nèi)部類成員:
/*System.out.println(data4);//這就報(bào)錯(cuò)了*/
InnerClass innerClass = new InnerClass();
System.out.println(innerClass.data4);
}
這個(gè)時(shí)候我們會(huì)發(fā)現(xiàn)內(nèi)部類在外部類中的實(shí)例化的時(shí)候居然與普通的類進(jìn)行實(shí)例化是一樣的
總結(jié):
獲取實(shí)例內(nèi)部類的對(duì)象的時(shí)候,依賴于外部類的對(duì)象;
要獲得實(shí)例內(nèi)部類對(duì)象,一定要先有外部類對(duì)象的訪問。
這個(gè)時(shí)候我們前面的代碼在內(nèi)部類中提到了一個(gè)靜態(tài)成員變量data6這個(gè)時(shí)候我們可以看一下這個(gè)代碼行的結(jié)果:
//外部類中也有靜態(tài)成員
class InnerClass{
public int data1 = 11111;
public int data4 = 4;
private int data5 = 5;
public static int data6 = 6;
public void test(){
System.out.println(data1);
System.out.println(data5);
System.out.println("內(nèi)部類的test方法");
}
}
等待我們執(zhí)行完成后會(huì)發(fā)現(xiàn)出現(xiàn)了錯(cuò)誤,

因?yàn)閟tatic修飾的成員是不依賴于類本身,并且是最先執(zhí)行的,而實(shí)例內(nèi)部類確實(shí)需要依賴于外部類的對(duì)象,實(shí)例內(nèi)部類是沒有獨(dú)立的儲(chǔ)存空間來儲(chǔ)存靜態(tài)成員,如果直接用static修飾的話,會(huì)造成外部類的執(zhí)行在加載的時(shí)候,導(dǎo)致訪問靜態(tài)成員會(huì)比較混亂,靜態(tài)成員是屬于類的,所以用final修飾我們可以確定是一個(gè)常量,并且表示對(duì)于開發(fā)者和編譯器來說這只有一個(gè)這樣的靜態(tài)成員
所以我們最后應(yīng)該寫成:
public static final int data6 = 6;
通過類名進(jìn)行訪問,我們可以得到這樣的結(jié)果:

2.(2).靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類就是在普通的類中用static來定義一個(gè)類
class OutClass2 {
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
static class InnerClass{
public int data4 = 4;
private int data5 = 5;
public static int data6 = 6;
public void test(){
System.out.println(data1);
System.out.println("內(nèi)部類的test方法");
}
}
}
這個(gè)時(shí)候我們看到這個(gè)靜態(tài)類中還有非靜態(tài)成員,但是我們用類名訪問只能是靜態(tài)成員和靜態(tài)方法,所以我們還需要把這個(gè)靜態(tài)類進(jìn)行實(shí)例化:
public class Test2 {
public static void main(String[] args) {
OutClass2.InnerClass innerClass = new OutClass2.InnerClass();
innerClass.test();
}
}
雖然我們把靜態(tài)內(nèi)部類進(jìn)行實(shí)例化,但是我們還需要訪問內(nèi)部類中的靜態(tài)成員最好還是用類名進(jìn)行訪問
我們執(zhí)行完了這個(gè)程序后,發(fā)現(xiàn)了報(bào)錯(cuò),我們?cè)?strong>靜態(tài)內(nèi)部類無法直接訪問外部類的非靜態(tài)成員變量

所以我們也可以在靜態(tài)內(nèi)部類中進(jìn)行實(shí)例化外部類,從而去訪問它的成員變量
static class InnerClass{
public int data4 = 4;
private int data5 = 5;
public static int data6 = 6;
public void test(){
OutClass2 outClass2 = new OutClass2();
System.out.println(outClass2.data1);
System.out.println(data4);
System.out.println(data5);
System.out.println(data6);
System.out.println("內(nèi)部類的test方法");
}
}
執(zhí)行完后我們可以看到,可以直接打印出來

我們會(huì)看到兩個(gè)特殊的變量:靜態(tài)變量data3和data6兩者
在外部類中也可以直接用內(nèi)部類的類名訪問其中的靜態(tài)成員變量data6
但是我們?cè)陟o態(tài)內(nèi)部類中可以直接訪問外部類中的靜態(tài)成員變量data3
static class InnerClass{
public int data4 = 4;
private int data5 = 5;
public static int data6 = 6;
public void test(){
System.out.println(data3);
}
}

在靜態(tài)內(nèi)部類中我們可以直接訪問外部類中的靜態(tài)成員變量,非靜態(tài)成員需要將外部類進(jìn)行實(shí)例化,才可以進(jìn)行訪問。
2.(3).匿名內(nèi)部類
匿名內(nèi)部類是沒有類名的一種類,但是前提也是根據(jù)接口,來實(shí)現(xiàn)的
那么我們需要首先定義一個(gè)接口:
interface A{
void testA();
}
public class Test2 {
public static void main(String[] args) {
new A(){
@Override
public void testA() {
System.out.println("X!");
}
}
}
}
這種方式就是匿名內(nèi)部類,這時(shí)候我們發(fā)現(xiàn)這個(gè)類沒有名字,實(shí)現(xiàn)了接口A,也重寫了接口A中的方法
那么這種類怎么去調(diào)用呢?
我們可以在尾大括號(hào)后加入了.testA(),便可以調(diào)用這個(gè)類

當(dāng)然我們可以像類一樣去進(jìn)行接口的**“實(shí)例化”**,得到了也是匿名內(nèi)部類,
public class Test2 {
public static void main(String[] args) {
int val = 10;
A a = new A() {
@Override
public void testA() {
System.out.println("值:" + val);
}
};
a.testA();
System.out.println(val);
}
}
去定義一個(gè)接口,去實(shí)現(xiàn)這個(gè)方法,但是我們依舊發(fā)現(xiàn)了這個(gè)類沒有名字
正常類應(yīng)該有class來定義,這里面的類沒有用class來定義,所以它依舊是匿名內(nèi)部類

如果我們這個(gè)時(shí)候去修改val的值,會(huì)發(fā)生什么呢?
public class Test2 {
public static void main(String[] args) {
int val = 10;
val = 100;
A a = new A() {
@Override
public void testA() {
System.out.println("值:" + val);
}
};
a.testA();
System.out.println(val);
}
}

可是執(zhí)行完之后我們發(fā)現(xiàn)它報(bào)錯(cuò)了。
因?yàn)樵谀涿麅?nèi)部類當(dāng)中能夠訪問的是沒有被修改過的數(shù)據(jù)
這種限制叫做變量的捕獲
所以默認(rèn)在匿名內(nèi)部類中能訪問的是被final修飾的變量,隱式的
2.(4).局部?jī)?nèi)部類
局部?jī)?nèi)部類:定義在方法的內(nèi)部
它不能被public,static等訪問限定符修飾
編譯器也有自己獨(dú)立的字節(jié)碼文件,命名格式:外部類名字$數(shù)字內(nèi)部類名字.class
這種類只能在方法體中使用:
public class Test4 {
public void func(){
class Inner{
public int data = 1;
}
Inner inner = new Inner();
System.out.println(inner.data);
}
public static void main(String[] args) {
Test4 test4 = new Test4();
test4.func();
System.out.println();
}
}

我們?cè)谌粘V泻苌儆玫骄植績(jī)?nèi)部類,所以我們?cè)谶@里不太過深入進(jìn)行講解。
總結(jié)
到此這篇關(guān)于Java中Object和內(nèi)部類的文章就介紹到這了,更多相關(guān)Java Object和內(nèi)部類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springBoot中的CORS跨域注解@CrossOrigin詳解
這篇文章主要介紹了springBoot中的CORS跨域注解@CrossOrigin詳解,通常,服務(wù)于?JS?的主機(jī)(例如?example.com)與服務(wù)于數(shù)據(jù)的主機(jī)(例如?api.example.com)是不同的,在這種情況下,CORS?可以實(shí)現(xiàn)跨域通信,需要的朋友可以參考下2023-12-12
java實(shí)現(xiàn)簡(jiǎn)單TCP聊天程序
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單TCP聊天程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
SWT(JFace) 簡(jiǎn)易瀏覽器 制作實(shí)現(xiàn)代碼
SWT(JFace) 簡(jiǎn)易瀏覽器 制作實(shí)現(xiàn)代碼2009-06-06
講解Java設(shè)計(jì)模式編程中的建造者模式與原型模式
這篇文章主要介紹了Java設(shè)計(jì)模式編程中的建造者模式與原型模式,設(shè)計(jì)模式有利于團(tuán)隊(duì)開發(fā)過程中的代碼維護(hù),需要的朋友可以參考下2016-02-02
JeecgBoot框架升級(jí)至Spring?Boot3的實(shí)戰(zhàn)步驟
本文主要介紹了JeecgBoot框架升級(jí)至Spring?Boot3的實(shí)戰(zhàn)步驟,從?2.7.10升級(jí)到3.1.5有以下幾個(gè)點(diǎn)需要注意,下面就來詳細(xì)的介紹一下,感興趣的可以了解一下2024-04-04
Java concurrency之AtomicLong原子類_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
AtomicLong是作用是對(duì)長(zhǎng)整形進(jìn)行原子操作。下面通過本文給大家介紹Java concurrency之AtomicLong原子類的相關(guān)知識(shí),感興趣的朋友一起看看吧2017-06-06
Java字符串操作技巧之語法、示例與應(yīng)用場(chǎng)景分析
在Java算法題和日常開發(fā)中,字符串處理是必備的核心技能,本文全面梳理Java中字符串的常用操作語法,結(jié)合代碼示例、應(yīng)用場(chǎng)景和避坑指南,可快速掌握字符串處理技巧,輕松應(yīng)對(duì)筆試面試高頻題目,感興趣的朋友一起看看吧2025-04-04
Java 實(shí)戰(zhàn)交易平臺(tái)項(xiàng)目之寵物在線商城系統(tǒng)
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實(shí)現(xiàn)一個(gè)寵物在線商城系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11

