Java中Object和內部類舉例詳解
前言:
上期內容為大家?guī)砹顺橄箢惻c接口的知識點的學習,這期內容我將為大家?guī)砹薕bject類與內部類的兩種類的學習,這其內容是對于類與對象的補充。
一、Object類
1.object類初識
Object類是Java中一個默認提供的類,Java里面內置了object類,其余所有的類都是具有繼承關系的,默認繼承Object父類,那么也就是所有的對象都是可以使用object的引用來接收。
所以也可以發(fā)生了向上轉型。
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);
}
}

這個時候我們可以看到打印的是對象所在位置的類型名和哈希值。

通過API文檔,我們可以看到object類中有許多方法,這些方法我們都要全部掌握!
但是我們已經(jīng)學習了clone方法了,這節(jié)我們要掌握三種方法,分別是:toString,hashCode,equals方法
hashCode我們后期在哈希表會再次進行詳細的講解。
2.Object的方法
2.(1).獲取對象的信息–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);
}

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

獲取的是對象所在位置的類型的名和哈希值。
如果我們想要打印對象的成員信息,這個時候我們可以重寫這個toString方法,去獲取對象成員變量的信息。
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方法,那么就會發(fā)生了多態(tài)。

2.(2).對象比較equals方法
在Java中,我們要比較兩者數(shù)據(jù)之間的是否一樣用等于號來進行,比如我們用==來進行比較,如果兩者不一樣,則返回假,一樣就返回真。
public static void main(String[] args) {
int a = 10;
int b = 10;
System.out.println(a == b);
}
//這個時候我們打印會的是true
那么我們比較兩個自定義類型呢?
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);
}

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

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

我們會發(fā)現(xiàn)它返回的是也是地址,比較判斷對象是否指向了一個同一個地址。
所以直接調用還是會為false但是我們不想讓它比較的是地址,就像上述代碼一般,兩個張三的實例化,就應該是同一個對象,所以這個時候就要涉及到重寫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);//比較兩者實例化后是不是一個person
//結果是false
//通過直接打印了它們的地址,發(fā)現(xiàn)兩者的地址不同
//所以 時候比較的就是變量中的值
System.out.println(person1.equals(person2));
}
}
這個時候我們重寫equals方法,這個時候的結果就是:true

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

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

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

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

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

我們可以看到它有OutClass和OutClass$InnerClass兩個字節(jié)碼文件
所以我們就可以知道內部類是外部類$內部類,當然還有一種是外部類$數(shù)字,我們會在后面講解。
2.內部類的分類:
有三種內部類:靜態(tài)、實例、匿名、局部 總共三種內部類;
使用頻率:實例>靜態(tài)>匿名>局部
2.(1).實例內部類
實例內部類就是在普通的類中去定義一個類,比如我們定義一個實例內部類:
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("內部類的test方法");
}
}
public void test(){
System.out.println("外部類的test方法");
}
}
我們定義一個實例內部類,這個時候我們來執(zhí)行這個實例內部類
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();
}
}
這時候就會顯示報錯:

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

這次執(zhí)行成功了,但是我們會發(fā)現(xiàn)了外部類與內部類均有data1,但是我們發(fā)現(xiàn)打印的是內部類中的data1,
這個時候就是前面所講的就近原則,Java的編譯器進行檢查的時候,發(fā)現(xiàn)內部類的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引用,這個時候表明這個data1是哪一類中的
System.out.println(OutClass1.this.data1);
System.out.println(data5);
System.out.println("內部類的test方法");
}
}

這個this就表明當前誰在調用這個內部類的成員變量,然后我們用外部類的this
那么可能會有人說,在外部類的方法中去訪問內部類的成員,我們可以先來看一下:

//這個時候IDEA也就會自動爆紅,顯示出錯,但是我們也可以來運行一下代碼,但是也會發(fā)現(xiàn)它們會報錯;

因為這個data4是在內部類中,我們沒有內部類的實例化,外部類是無法訪問的其內部類中的成員變量的
所以我們需要先進行內部類的實例化才可以執(zhí)行這個代碼:
public void test(){
System.out.println("外部類的test方法");
//訪問內部類成員:
/*System.out.println(data4);//這就報錯了*/
InnerClass innerClass = new InnerClass();
System.out.println(innerClass.data4);
}
這個時候我們會發(fā)現(xiàn)內部類在外部類中的實例化的時候居然與普通的類進行實例化是一樣的
總結:
獲取實例內部類的對象的時候,依賴于外部類的對象;
要獲得實例內部類對象,一定要先有外部類對象的訪問。
這個時候我們前面的代碼在內部類中提到了一個靜態(tài)成員變量data6這個時候我們可以看一下這個代碼行的結果:
//外部類中也有靜態(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("內部類的test方法");
}
}
等待我們執(zhí)行完成后會發(fā)現(xiàn)出現(xiàn)了錯誤,

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

2.(2).靜態(tài)內部類
靜態(tài)內部類就是在普通的類中用static來定義一個類
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("內部類的test方法");
}
}
}
這個時候我們看到這個靜態(tài)類中還有非靜態(tài)成員,但是我們用類名訪問只能是靜態(tài)成員和靜態(tài)方法,所以我們還需要把這個靜態(tài)類進行實例化:
public class Test2 {
public static void main(String[] args) {
OutClass2.InnerClass innerClass = new OutClass2.InnerClass();
innerClass.test();
}
}
雖然我們把靜態(tài)內部類進行實例化,但是我們還需要訪問內部類中的靜態(tài)成員最好還是用類名進行訪問
我們執(zhí)行完了這個程序后,發(fā)現(xiàn)了報錯,我們在靜態(tài)內部類無法直接訪問外部類的非靜態(tài)成員變量

所以我們也可以在靜態(tài)內部類中進行實例化外部類,從而去訪問它的成員變量
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("內部類的test方法");
}
}
執(zhí)行完后我們可以看到,可以直接打印出來

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

當然我們可以像類一樣去進行接口的**“實例化”**,得到了也是匿名內部類,
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);
}
}
去定義一個接口,去實現(xiàn)這個方法,但是我們依舊發(fā)現(xiàn)了這個類沒有名字
正常類應該有class來定義,這里面的類沒有用class來定義,所以它依舊是匿名內部類

如果我們這個時候去修改val的值,會發(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)它報錯了。
因為在匿名內部類當中能夠訪問的是沒有被修改過的數(shù)據(jù)
這種限制叫做變量的捕獲
所以默認在匿名內部類中能訪問的是被final修飾的變量,隱式的
2.(4).局部內部類
局部內部類:定義在方法的內部
它不能被public,static等訪問限定符修飾
編譯器也有自己獨立的字節(jié)碼文件,命名格式:外部類名字$數(shù)字內部類名字.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();
}
}

我們在日常中很少用到局部內部類,所以我們在這里不太過深入進行講解。
總結
到此這篇關于Java中Object和內部類的文章就介紹到這了,更多相關Java Object和內部類內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Eclipse中引入com.sun.image.codec.jpeg包報錯的完美解決辦法
Java開發(fā)中對圖片的操作需要引入 com.sun.image.codec.jpeg,但有時引入這個包會報錯,利用下面的操作可以完成解決這個問題2018-02-02
Java微信公眾平臺開發(fā)(2) 微信服務器post消息體的接收
這篇文章主要為大家詳細介紹了Java微信公眾平臺開發(fā)第二步,微信服務器post消息體的接收,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04

