目录
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.代码块的概念
使用 {} 定义的一段代码称为代码块。
根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块
- 构造代码块(实例代码块)
- 静态代码块
- 同步代码块(后续讲解多线程部分再谈)
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源码