类和对象基础

本文详细介绍了面向对象编程的基本概念,包括类的定义与使用、对象的实例化、this引用、构造方法、封装、初始化以及静态成员。通过实例和经典题目解析,阐述了对象的创建、成员变量的访问权限、构造方法的作用以及静态成员的特点。此外,还探讨了内部类的分类及其使用,如实例内部类、静态内部类、局部内部类和匿名内部类。最后,讨论了对象的打印方法,强调了重写toString方法的重要性。
摘要由CSDN通过智能技术生成

目录

1. 面向对象的初步认知

1.1什么是面向对象

1.2面向对象和面向过程

2. 类定义和使用

2.1类定义格式

3. 类的实例化

4. this引用

4.1.什么是this引用

4.2.this引用的特性

5. 对象的构造及初始化

构造方法

默认初始化

就地初始化

6. 封装

6.1.封装的概念

6.2.访问限定符

6.3封装扩展之包

6.3.1包的概念

6.3.2.导入包中的类

6.3.3.自定义包

6.3.4常见的包

7. static成员

8. 代码块

8.1.代码块的概念

8.2 普通代码块

8.3 构造代码块

8.4 静态代码块

​编辑

几道经典题目讲解

9. 内部类

9.1内部类分类

9.2实例内部类

9.3静态内部类

9.4局部内部类

9.5匿名内部类

10. 对象的打印


1. 面向对象的初步认知

Java里一切皆对象

1.1什么是面向对象

面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。

OOP:面向对象程序(语言),继承/封装/多态

1.2面向对象和面向过程

面向过程:注重完成任务需要的步骤的每一个过程。

面向对象:以面向对象方式来进行处理,就不关注完成事情的具体过程,通过对象之间的交互来完成。

面向对象的经典例子:大象放进冰箱

2. 类定义和使用

2.1类定义格式

// 创建类
class ClassName{
field; // 字段(属性) 或者 成员变量
method; // 行为 或者 成员方法
}

class为定义类的关键字,ClassName为类的名字,{}中为类的主体。

字段(属性)定义在方法的外部类的内部

例如:

eg1:

class Person{
    //属性
    public int age;
    public String name;

    //行为
    public void eat(){
        System.out.println("吃饭");
    }
}

eg2:

class WashMachine{
public String brand; // 品牌
public String type; // 型号
public double weight; // 重量
public double length; // 长
public double width; // 宽
public double height; // 高
public String color; // 颜色
public void washClothes(){ // 洗衣服
System.out.println("洗衣功能");
}
public void dryClothes(){ // 脱水
System.out.println("脱水功能");
}
public void setTime(){ // 定时
System.out.println("定时功能");
}
}

命名规范:

类名:大驼峰

变量名/方法名:小驼峰

注意事项:

1.类名注意采用大驼峰定义

2.成员前写法统一为public

3.此处写的方法不带 static 关键字

eg3:

class PetDog {
public String name;//名字
public String color;//颜色
// 狗的属性
public void barks() {
System.out.println(name + ": 旺旺旺~~~");
    }
// 狗的行为
public void wag() {
System.out.println(name + ": 摇尾巴~~~");
    }
}

eg4:

public class Student{
public String name;
public String gender;
public short age;
public double score;

public void DoClass(){}
public void DoHomework(){}
public void Exam(){}

}

1. 一般一个文件当中只定义一个类

2. main方法所在的类一般要使用public修饰(IDEA中可以不需要)(Eclipse默认会在public修饰的类中找main方法)

3. public修饰的类必须要和文件名相同,如果要修改类名,必须同时修改文件名

IDEA中演示:

3. 类的实例化

定义了一个,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型

//实例化一个Dog对象
Dog dog = new Dog();

.访问:

dog.name = "来福";
dog.color = "黑色";

Dog dog = new Dog();
dog.name = "来福";
dog.color = "黑色";

System.out.println(dog.color);
System.out.println(dog.name);

class Dog{
    //成员变量
    public String name;
    public String color;

    public void barks(){
        System.out.println(name+" 旺旺");
    }
    public void wag(){
        System.out.println(name + " 摇尾巴");
    }
}
public class Test {
    public static void main(String[] args) {
        //实例化一个Dog对象
        Dog dog = new Dog();
        dog.name = "来福";
        dog.color = "黑色";

        System.out.println(dog.color);
        System.out.println(dog.name);
        dog.barks();
        dog.wag();
    }
}

 

实例多个对象:

 

 

每次new一个对象就会开辟一块新的空间,实例化出的对象 占用实际的物理空间,存储类成员变量

在Java中:局部变量必须要初始化才能使用,而成员变量没有初始化的时候,仍然可以使用,默认值就是对应的初始值,引用类型:null,float:0.0,int:0,boolean:false,char:'\u0000'

1.new 关键字用于创建一个对象的实例.

2.使用 . 访问对象中的属性和方法,. +方法 相当于是调用.

3.同一个类可以,可以通过new关键字,实例化无数个对象.

4.对象中是不存在方法的,方法是不占内存的,只有在调用方法的时候才会开辟栈帧.

4. this引用

public class TestDate {
    public int year;
    public int month;
    public int day;

    public void setDate(int y,int m, int d){
        year = y;
        month = m;
        day = d;
    }
    public void printDate(){
        System.out.println(year+"-"+month+"-"+day);
    }

    public static void main(String[] args) {
        TestDate testDate = new TestDate();
        //创建了一个实例化对象,但没有赋值   默认都是0
    }
//第一种方法赋值
testDate.year=2022;
testDate.month=11;
testDate.day=13;
//第二种方法赋值:公开的方法
testDate.setDate(2022,8,4);

printDate不用传参,printDate打印的都是成员变量,用的都是同一份成员变量,在同一个类中可以直接访问

当形参名与成员变量名相同的时候:

public void setDate(int year, int month, int day) {
    year = year;
    month = month;
    day = day;
}

没有赋值

局部变量优先,在setDate中相当于形参自己给自己赋值,根本没有赋值到成员变量当中

解决方法:

前面加个this.

public void setDate(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
}

这就引出this的用法

4.1.什么是this引用

this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

TestDate testDate1 = new TestDate();
TestDate testDate2 = new TestDate();
TestDate testDate3 = new TestDate();

testDate1.setDate(2022,8,13);
testDate2.setDate(2023,8,13);
testDate3.setDate(2024,8,13);

testDate1.printDate();
testDate2.printDate();
testDate3.printDate();

以上三个对象都在调用setDate和printDate函数,但是这两个函数中没有任何有关对象的说明,setDate和printDate函数如何知道设置/打印的是哪个对象的呢?

方法一:

. 前面是谁,就设置/打印

方法二:

每个方法默认会有一个隐藏(可以不写)的参数

public void setDate(TestDate this, int yeat, int month, int day)

public void printDate(TestDate this)

this:当前对象的引用:谁调用这个方法,谁就是this。

 testDate1引用TestDate()对象,this就代表当前new TestDate()对象的引用。

this的三种使用方法

this.成员变量

this.访问成员方法

this();访问构造方法

this调用成员方法,这时候谁调用func就是谁的this

4.2.this引用的特性

1. this的类型:对应类型引用,即哪个对象调用就是哪个对象的引用类型

2. this只能在"成员方法"中使用(有些成员方法里面不能用,比如静态成员方法)

3. 在"成员方法"中,this只能引用当前对象,不能再引用其他对象

4. this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法 对象的引用传递给该成员方法,this负责来接收

this的本质:所在方法调用者的地址值

所以this一定是在成员方法中使用的,可以用来区分局部变量和成员变量

小练习:

写一个学生类:

成员变量:姓名,年龄

成员方法:设置成员变量的值的方法,另外一个方法通过this调用这个方法

public class TestStudent {
    public String name;
    public int age;

    public void func2(){
        this.set("小黄",18);
    }

    public void set(String name,int age){
        this.name = name;
        this.age = age;
        this.print();
    }

    public void print(){ //这里的this可以不用写  因为该方法内没有局部变量
        System.out.println("name:"+this.name+"age:"+this.age);
    }


    public static void main(String[] args) {
        TestStudent testStudent = new TestStudent();

        testStudent.func2();//通过对象调用成员方法
    }
}

this不一定每次都要写,但是建议都写上

5. 对象的构造及初始化

构造方法

构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,且没有返回值

在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次

 

//带参数的构造方法
TestStudent(String name, int age){
    this.name = name;
    this.age = age;
}

可以通过构造方法传参对变量进行赋值

那么构造方法如何被调用呢?

不难发现

 当我们没有提供任何构造方法的时候,编译器会帮我们提供一个不带参数的构造方法

只有实例化对象的时候,才会调用构造方法,而且在调用构造方法的时候可以传参,对成员进行赋值。

当构造方法调用完成之后,对象才实际上产生了。

 当我们提供了任何一个构造方法的时候,编译器就不会在提供不带参数的构造方法


且构造方法是可以被重载

 通过this();来访问构造方法

注意点:


 this()必须在构造方法里,在其他方法内访问构造方法,就会报错.

.构造方法中,可以通过this调用其他构造方法来简化代码

public class Date {
public int year;
public int month;
public int day;
// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(1900,1,1);必须是构造方法中第一条语句
public Date(){
//System.out.println(year); 注释取消掉,编译会失败
this(1900, 1, 1);
//this.year = 1900;
//this.month = 1;
//this.day = 1;
}
// 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
    }
}


 并且不能构成循环调用(环),类似上面:你调用我,我调用你。


总结:构造方法的特性

1.名字必须与类名相同

2.没有返回类型,写成void也不行

3.创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次

4.构造方法可以重载

5.如果用户没有显式定义任何构造方法,编译器会生成一份默认的无参的构造方法

默认初始化

成员变量没有初始化,会有一个默认的初始值

就地初始化

 在声明成员变量时,就直接给出了初始值。

适用于定义所有对象默认相同的东西。

生成构造方法快捷键:

6. 封装

6.1.封装的概念

面向对象程序三大特性:封装、继承、多态。

封装:将数据操作数据的方法进行有机结合,隐藏对象的属性和实现细节,对外只是提供一些公开的接口来和对象进行交互

简洁一点就是:

内部的实现细节,进行隐藏/封装,对外只提供一些公开的接口(方法),供其他的用户进行访问

6.2.访问限定符

要实现封装 - 就要修改对一些数据的权限        ,这些权限由访问修饰限定符来修改。

访问修饰限定符:

在同一个package里面不能定义两个相同的类名

对于已经定义的类,如果其中一个成员变量的名字需要修改,那么下面所有的引用都需要修改名字

比较麻烦,因为public修饰的字段在哪里都可以访问        

所以一般被public修饰的字段不轻易修改        

 private修饰的字段只能在当前类内部访问

name被private修饰后,说明name只能在当前类的内部进行访问,但是可以在该类中定义一个用来修改名字的公开的方法来访问,给name赋值—— 方法和数据进行的结合,这就是封装,对类内的一些细节隐藏,不想在类外看到的,都进行了封装,类外只提供了一些公开的方法(接口)

       

如果字段一多,一个一个写方法太麻烦了,这里可以使用快捷键,由编译器自己生成

总结:对类的内部的细节进行了隐藏,对类外提供了公开的接口(方法),这就是封装

public 和 private就像两个极端,public哪里都可以用,private只能是当前类当中才能使用


private也可以修饰方法/构造方法

若修饰的是构造方法,那么在类外就无法实例化对象了,所以一般情况下构造方法写成public

但是写成private也可以实例化对象,这个方法以后再说


public:可以理解为一个人的外貌特征,谁都可以看得到

private:只有自己知道,其他人都不知道

default:对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了

如果定义的时候前面没有加任何修饰(不能自己主动加default),就是默认权限

默认权限的范围是同一个(文件夹)里面

这里就引出包的概念

比如:jdk1.8中  util - 工具  在util包里面可以找到所有跟工具相关的类

用包来更好的管理这些类

6.3封装扩展之包

6.3.1包的概念

为了更好的管理类,把多个类收集在一起成为一组,称为软件包。

包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式

在同一个工程中允许存在相同名称的类,只要处在 不同的包 中即可。

创建新package的方法:

 

在新创建的包里面新创建一个java类,开头会出现package 文件名,用来声明该java类是在当前package底下的

 基本规则:

1.在文件的最上方加上一个package语句指定该代码在哪个包中

2.包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式(eg:com.baidu.www)

3.包名要和代码路径名相匹配(且是小写的),例如创建com.bit.demo1的包,那么就会存在一个对应的路径com/bit/demo1来存储代码

4.如果一个类没有package语句,则该类被放在一个默认包(src)中

6.3.2.导入包中的类

Java 中已经提供了很多现成的类供我们使用. 例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date 类.

public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();//显示的方法导入包
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
    }
}

 但是这种写法比较麻烦一些, 可以使用 import语句导入包.(import只能导入某个包中某个具体的不能直接导入整个包)

import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
    }
}

 如果需要使用 java.util 中的其他类, 可以使用 import java.util.*

import java.util.*;

*代表所有包,但不是一下子全导入,而是将来用到谁就导入谁

但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况.

import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
    }
}

// 编译出错

Error:(5, 9) java: 对Date的引用不明确

java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

说白了就是两个包里面都有Date,编译器就懵了,不知道你要导哪个。

这时候就需要具体说明用哪个包(第一种方式:显示的方法导包)

这写包都需要我们自己导入的,这时候就需要查看帮助手册,在帮助手册中查看在哪进行导入


使用import static导入包中静态的方法和字段。

sqrt和pow都是Math这个类的静态方法,静态方法可以拿类名直接调用

import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2));//省略了Math.
System.out.println(result);
}
}

 但是以后很少用到


总结:用到哪个类,就需要导入对应的包底下的类。

6.3.3.自定义包

自己新建一个包来组织


这时候我们在回来看default访问权限(也叫包访问权限),说白了就是同一个包(文件夹)底下的类才能访问

比如:

public class Test {
    int a = 10
}

 在Test类中定义一个变量a(默认为default访问权限)

public class Test2 {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.a);
    }
}

同一个包另一个类Test2中可以访问a

处于不同包之间就不可以访问,但是可以实例化

如果想要在不同的包内访问 - 导包就可以,且需要显示指定一下包名

但是默认包(src)底下的类是实例化不了的比如在demo1包(自定义包)底下的Test类内是实例化不了默认包(src)底下的Test类的,但是默认包(src)底下的类是可以实例化自定义包底下的类的。

这些访问修饰限定符,只是在权限上进行了限制,并没有说不能用在成员变量,成员方法 ,构造方法,只是来修饰权限的。

6.3.4常见的包

  • 1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入
  • 2. java.lang.reflect:java 反射编程包;
  • 3. java.net:进行网络编程开发包。
  • 4. java.sql:进行数据库开发的支持包。
  • 5. java.util:是java提供的工具程序包。(集合类等) 非常重要
  • 6. java.io:I/O编程开发包。        

以后需要用到某个类的时候,需要在帮助手册 - API中查找该类在对应的哪个包底下

7. static成员

创建了三个学生对象

如果这三个学生在同一个班级,那么就可以用static修饰班级成员变量

此时班级变量classes就不在堆上(不在对象里)了,在方法区

此时classes变量就不属于某一个对象的,classes的赋值是通过类名.classes进行赋值

比如:

由于classes不在对象里,不属于对象,而是属于类,所以classes这里又叫做静态成员变量/类变量

访问:类名 . 去访问

既然静态成员变量不在对象里,就不能通过this.来访问,但是强制写上也不会报错

静态的是不依赖对象的


总结:

不加static修饰的方法,必须通过对象.来访问

加了static修饰的方法,还可以通过类名来访问, 但是一般不通过对象来访问静态成员变量,而是通过类名来访问。


        同理:静态方法也一样,需要用类名(这里是Student)去访问静态方法,而不是对象

 

 而不加static的方法就必须通过对象 . 来访问


 在静态方法内部不能直接访问非静态数据成员和成员方法        

 

 func2不存在对象的引用,所以就不存在this
 


那static属性应该如何访问呢?

Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。

public class Student{
// ...
private static String classRoom = "Bit306";
// ...
public static String getClassRoom(){
return classRoom;
    }
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.getClassRoom());
    }
}

总结:

1.访问方式:通过类名

2.静态的,不依赖对象

3.静态方法内部,不能调用非静态的成员或者变量,存在方法区,只有一份

static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。      

 特性:

1. 不属于某个具体的对象,是类变量,存储在方法区中

2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者

3. 不能在静态方法中访问任何非静态成员变量

4. 静态方法中不能调用任何非静态方法(反过来,非静态方法中可以调用静态方法),因为非静态方法有this参数,在静态方法中调用时候无法传递this引用。

5.生命周期伴随类的一生(随着类的加载而加载,随着类的销毁而销毁)

6. 静态方法无法重写,不能用来实现多态(此处大家暂时不用管,后序多态位置详细讲解)。

特性3:

public static String getClassRoom(){
System.out.println(this);
return classRoom;
}
// 编译失败:Error:(35, 28) java: 无法从静态上下文中引用非静态 变量 this
public static String getClassRoom(){
age += 1;
return classRoom;
}
// 编译失败:Error:(35, 9) java: 无法从静态上下文中引用非静态 变量 age

特性4:

public static String getClassRoom(){
doClass();
return classRoom;
}
// 编译报错:Error:(35, 9) java: 无法从静态上下文中引用非静态 方法 doClass()

static 成员变量初始化

注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性

1. 就地初始化 就地初始化指的是:在定义时直接给出初始值

public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "Bit306"; 
// ...
}

2.在类外通过类名.引用初始化

3. 在静态代码块中初始化

代码块在接下来讲解

8. 代码块

8.1.代码块的概念

使用 {} 定义的一段代码称为代码块。

根据代码块定义的位置以及关键字,又可分为以下四种:        

  1. 普通代码块
  2. 构造代码块(实例代码块)
  3. 静态代码块
  4. 同步代码块(后续讲解多线程部分再谈)

8.2 普通代码块

普通代码块:定义在方法中的代码块.

public class Main{
public static void main(String[] args) {
{ //直接使用{}定义,普通方法块
int x = 10 ;
System.out.println("x1 = " +x);
}
int x = 100 ;
System.out.println("x2 = " +x);
    }
}
// 执行结果
x1 = 10
x2 = 100

这种用法较少见

8.3 构造代码块

构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。

public class Student{
//实例成员变量
private String name;
private String gender;
private int age;
private double score;
public Student() {
System.out.println("I am Student init()!");
}
//实例代码块
{
this.name = "bit";
this.age = 12;
this.sex = "man";
System.out.println("I am instance init()!");
}
public void show(){
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
    }
}
public class Main {
public static void main(String[] args) {
Student stu = new Student();
stu.show();
    }
}
// 运行结果
I am instance init()!
I am Student init()!
name: bit age: 12 sex: man

8.4 静态代码块

使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。

public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom;
//实例代码块
{
this.name = "bit";
this.age = 12;

this.gender = "man";
System.out.println("I am instance init()!");
}
// 静态代码块
static {
classRoom = "bit306";
System.out.println("I am static init()!");
}
public Student(){
System.out.println("I am Student init()!");
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
    }
}

注意事项:

1.静态代码块不管生成多少个对象,其只会执行一次

2.静态成员变量是类的属性,因此是在JVM加载时开辟空间并初始化的

3.如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)

4.实例代码块只有在创建对象时才会执行


public class Student {
    static{
        System.out.println("静态代码块");
    }

    {
        System.out.println("实例代码块/构造代码块");
    }
    public Student(){
        System.out.println("空参的构造方法");
    }

    public static void main(String[] args) {
        Student student = new Student();//执行空参的构造方法
    }
}

执行顺序(与代码定义的顺序无关)

优先执行静态代码块,静态代码块类加载的时候就会被执行

static修饰的加载速度会比较快,且一定会被加载/执行(static的优势)


Student student = new Student();
System.out.println("================");
Student student2 = new Student();

且静态代码块只会被执行一次


静态代码块的出现主要是为了初始化静态成员

如果在静态代码块初始化的同时,又进行了就地初始化,执行的顺序就是代码定义的顺序  

public static String classes = "二班"; //先定义

static{//后定义
    classes = "一班";
    System.out.println("静态代码块");
}

 classes最后是结果是一班



几道经典题目讲解

topic1:

class A{
    public static void hello(){
        System.out.println("hello");
    }
}
public class Test {
    public static void main(String[] args) {
        A a = null;//a这个引用 不指向任何对象
        a.hello();
    }
}

如果hello方法没有被static修饰,代码一定会报错,空指针异常.

但是这里hello方法被static修饰了,属于静态成员方法,不依赖对象,只需要类名就可以调用,这里只要a的类型是属于A的就可以调用,无论a这个引用是否指向任何对象

topic2:

public class Test {
    public int aMethod(){
        static int i = 0;//错误点
        i++;
        return i;
    }
    public static void main(String[] args) {
        Test test = new Test();
        test.aMethod();
        int j = test.aMethod();
        System.out.println(j);
    }
}

static修饰的变量是类的变量/方法,不能修饰局部变量,而不是方法的局部变量

topic3:

public class Test {
    static boolean Paddy;//静态成员变量 没有初始化
    public static void main(String[] args) {
        System.out.println(Paddy);
    }
}

此时代码可以直接输出,不用类名.访问,它们都属于同一个作用域(同一个类里面),若这里不加static修饰成员变量就会报错,因为静态的访问时不依赖对象的。

 topic4:

public class Test {
    private  float f = 0.1f;
    int m = 12;
    static int n = 1;
    
    public static void main(String[] args) {
        Test test = new Test();

    }
}

以下在main函数中正确的时:

a.t.f  = 3.0

b.this = n

c.Test.m

d.Test.n

a.应该时3.0f

b.n被static修饰,是静态成员变量,不依赖对象,通过类名.n访问

c.普通成员变量,不能通过类名.访问,要通过对象.

d.正确

topic5:

public class Test {
    private static int x = 100;

    public static void main(String[] args) {
        Test a = new Test();
        a.x++;
        Test a2 = new Test();
        a2.x++;
        a = new Test();//a 引用新的对象
        a.x++;
        Test.x--;
        System.out.println("x="+x);
    }
}

很经典的一题

x被private和static修饰,权限是私有的(只能在当前类内访问),且是静态成员变量(只有一份)(访问不依赖对象).         

1.实例化对象a

2.a.x++;通过对象a访问静态成员变量(只要a是属于Test就可以访问成功) ,x变为101

3.实例化对象a2

4.a2.x++;同理,x变为102

5.a = new Test(); a这个引用指向新的对象(但是a仍然是属于Test的变量)

6.a.x++(易错),虽然此时a引用了新的对象,但是a本身还是属于Test这个类的,只要是属于Test这个类的变量就可以访问通过对象.来访问静态成员变量(虽然不是最正确的访问静态成员变量二点方式,但是编译器也不会报错),x变为103

7.Test.x--;静态成员变量最正确的引用方式,x变为102;

所以最后结果输出为x = 102  

topic6:     

public class Test {
    private int count;

    public static void main(String[] args) {
        Test test = new Test(88);
        System.out.println(test.count);
    }
    Test(int a){
        count = a;
    }
}

此题考构造方法,最后输出count的值为88

topic7:

public class Test {
    static int cnt = 6;
    static{
        cnt += 9;
    }

    public static void main(String[] args) {
        System.out.println("cnt = "+cnt);
    }
    
    static{
        cnt /= 3;
    }
}

执行顺序:静态代码块 -> 实例代码块 - > 空参构造方法

这里全是静态代码块,定义的顺序就是执行顺序,cnt = 6 然后+= 9 变为15,最后/=3 变为5

5就是最后的结果

9. 内部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服 务,那么这个内部的完整结构最好使用内部类。

可以将一个类定义在另一个类或者一个方法的内部, 前者称为内部类,后者称为外部类。

public class OutClass {
class InnerClass{
    }
}
// OutClass是外部类
// InnerClass是内部类

9.1内部类分类

  • 实例内部类(非静态内部类/构造内部类)
  • 静态内部类
  • 局部内部类(几乎不用)
  • 匿名内部类

9.2实例内部类

class OutterClass{

    public int data1;
    public int data2;
    public int data3;

    public void test(){
        System.out.println("OutterClass::test()");
    }

    /**
     *  实例内部类
     *  1.如何获取实例内部类的对象
     *  2.
     */

    //实例内部类(非静态内部类/构造内部类)
    class InnerClass{
        public int data4;
        int data5;
        //public static data6;   // err

        public void func(){
            System.out.println("InnerClass::func()");
        }
    }
}

如何获取实例内部类的对象呢

可以把这个InnerClas想象成一个普通的实例成员,需要一个OutterClass外部类对象才能调用实例成员

同理:

public class Test {
    public static void main(String[] args) {
        OutterClass outterClass = new OutterClass();
        System.out.println(outterClass.data1);//通过对象访问成员变量
        //同理  实例内部类可以当作OuttetClass外部类的成员
        OutterClass.InnerClass innerClass = outterClass.new InnerClass();
                                           //通过对象.访问成员变量
}

核心就是:

OutterClass outterClass = new OutterClass();
OutterClass.InnerClass innerClass = outterClass.new InnerClass();

也可以换成:

OutterClass.InnerClass innerClass = new OutterClass().new InnerClass();

将InnerClass看作是外部类的一个成员变量,所以必须要通过外部类的对象来访问


注意点:

在内部类中无法创建static修饰的成员变量,因为InnerClass的实例化需要外部类对象的引用,而static修饰的成员变量在类加载的时候加载的,所以static加载的时间一定是先与InnerClass的实例化的

class OutterClass{

    class InnerClass{
        public static int data6 = 10;  // err
    }

}

总结:实例内部类当中,不能有静态的成员变量


但是实例内部类中比较特殊:

class OutterClass{

    class InnerClass{
        public static final int data6 = 10; //right
    }

}

再加上一个final就不会报错了(如果不是实例内部类,加上了final也会报错),因为加上了final,data6相当于是一个常量常量在编译的时候就确定了,一旦初始化就不能进行修改

public void func(){
    static final int c = 10; // 若不是实例内部类,加上final修饰也不行 - 语法规定
}

final是常量的意思,类似与c语言中的const

public class Test {
    public static void main(String[] args) {
        final int a = 10;
        a = 20; //err
    }
}

加上了final修饰,a的值就不能被改变了

一般定义常量名都是大写的


总结:实例内部类中不能有静态成员变量,如果一定要有,则必须加上final修饰


注意点2:就近原则(与局部变量相似)

class OutterClass{
    public int a = 1;

    class InnerClass{
        public int a = 111;
        
        public void func(){

        System.out.println(data1);

      }
   }
}

调用func结果是111

那么在实例内部类中,如何访问外部类中,名字相同的成员变量?

拿到外部类的this

public void func(){
        
        System.out.println(OutterClass.this.data1);

}

同理:

外部类中要访问实力内部类中的成员,必须也要创建内部类的对象才能访问

总结:

1. 外部类中的任何成员(无论权限)都可以在实例内部类方法直接访问(属于同一个类)

2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束(可以给内部类加上权限访问限定符)

3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问

4. 实例内部类对象必须在先有外部类对象前提下才能创建

5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用(OutterClass.this)

6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象

实例化内部类对象,需要有外部类对象的引用,能不能不用外部类对象的引用?

把内部类定义成静态内部类

这就引出静态内部类

9.3静态内部类

class OutterClass2{

    static class InnerClass2{
        public int data4 = 4;
        int data5 = 5;
        public static int data6 = 6;

        public void func(){
            System.out.println("InnerClass::func()");
        }
    }

public class Test2 {
    public static void main(String[] args) {
        OutterClass2.InnerClass2 innerClass2 = new OutterClass2.InnerClass2();
        //没有实例化外部类对象 就可以实例化内部类对象
        }

    }
}

没有实例化外部类对象就可以实例化内部类对象,比较方便

所有静态内部类有时候出现的概率会比较高一点,因为不需要实例化外部类的对象

OutterClass2.InnerClass2 innerClass2 = new OutterClass2.InnerClass2();

注意new后面OutterClass2.InnerClass2 是一个整体


静态内部类中,不能访问外部类的非静态成员。外部类的非静态成员,需要外部类的对象的引用才能访问

class OutterClass2{
    public int data1 = 1;
    int data2 = 2;
    public static int data3 = 3;

    public void test(){
        System.out.println("OutterClass::test()");
    }


    static class InnerClass2{
        public int data4 = 4;
        int data5 = 5;
        public static int data6 = 6;

        public void func(){
            System.out.println("InnerClass::func()");
            System.out.println(data1);//err
            System.out.println(data2);//err
            System.out.println(data3);
            System.out.println(data4);
            System.out.println(data5);
            System.out.println(data6);
        }
    }

如果非要访问,创建一个外部类对象,通过外部类对象的引用

OutterClass2 outterClass2 = new OutterClass2();
System.out.println(outterClass2.data1);//right
System.out.println(outterClass2.data2);//right

总结:静态的里面是不能有非静态的

9.4局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少

public class OutClass {
int a = 10;
public void method(){
int b = 10;
// 局部内部类:定义在方法体内部
// 不能被public、static等访问限定符修饰
class InnerClass{
public void methodInnerClass(){
System.out.println(a);
System.out.println(b);
    }
}
// 只能在该方法体内部使用,其他位置都不能用
InnerClass innerClass = new InnerClass();
innerClass.methodInnerClass();
}
public static void main(String[] args) {
// OutClass.InnerClass innerClass = null; 编译失败
    }
}

注意事项

1. 局部内部类只能在所定义的方法体内部使用

2. 不能被public、static等修饰符修饰

3. 编译器也有自己独立的字节码文件,命名格式:外部类名字 $数字 内部类名字.class

4. 几乎不会使用

9.5匿名内部类

 匿名内部类

学完接口会详细讲解

10. 对象的打印

重写toString方法

public class Person {
    public int age = 18;
    public String name = "小黄";

    public void show(){
        System.out.println("age:"+age+"name:"+name);
    }

    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person);//直接打印person这个局部变量
    }
}

若直接打印person这个局部变量,结果是所引用对象的地址值

Person是输出变量的全路径

@符号后面跟的数字是经过哈希变换之后的地址值,和地址一样是唯一的

public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

println源码

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

valueOf源码

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

toString源码

java比较安全,拿不到栈上的地址,堆区上的地址也只能拿到通过哈希变换的地址。

//重写toString方法
@Override
public String toString() {
    return "Person{" +
            "age=" + age +
            ", name='" + name + '\'' +
            '}';
}

当我们自己实现了toString方法,编译器会调用我们自己写的toString方法

上面的@Override会帮你检查方法名/返回值 - 注解,要想编译器调用我们自己写的toString方法,就必须保持方法名字/返回值都一致,否则编译器找不到就会调用toString源码

  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值