学习目标
本讲主要介绍如何在Java中设计和使用类的基础知识,包括:
类的初始化方法,
“==”的含义,
this的用法,
装箱与拆箱,
……
其中,理解对象变量的含义是关键。
本讲还介绍了javap反汇编.class文件的基本方法。 在课堂上会引导大家阅读Java字节码指令。
本讲属于Java面向对象编程最为基础的内容,必须牢固掌握。
编写类的“模版”
public/private class 类名
{
public/private 数据类型 变量名;
public/private 数据类型 方法名(参数列表){}
}
两种最基本的存储权限:
public:存储和访问不受限制
private:除非是属于类自己的方法,外界代码不能存取和访问
定义Java类的示例
某些编程语言比如(C#)为“属性”(property)提供了单独的定义方式,在Java中我们可以通过组合一个私有字段和一对get/set方法来定一个属性。
class Myclass{
public String Information="";
pubic void myMethod(String argu)
{
System.out.println(argu);
}
private int value;
public int getValue()
{
return value;
}
public void setValue(int value)
{
this.value=value;
}
使用自定义类
小结
- 我们需要定义一个对象变量
- 然后“创建new”一个对象,赋值给对象变量
- 现在就可以通过对象变量使用对象,主要方式有:
直接调用类的方法/存取类的字段
一种新的变量类型
通过对int value = 100;
和Myclass obj = new Myclass();
两种方式定义变量,这两种方式定义的变量是一样的吗?
引用类型vs原始数据类型
- “引用”一个对象的变量称为“引用类型”的变量,即对象变量
- int、float之类的称为原始数据类型
变量与内存分配
- 当声明一个对象变量时没有马上创建一个对象,此变量=null
- 定义一个原始类型的变量,马上给其分配内存空间
变量的初始化
Java定义变量时,要求必须显式初始化变量。
int value;
system.out.println(value);//wrong
int value=100;
system.out.println(value);//correct
对象变量的初始化
对象变量如果不引用一个真实的对象,则必须声明为null
Myclass obj;
system.out.println(obj.toString());//wrong
Myclass obj=null;
system.out.println(obj.toString());//correct
对象变量的特殊值null:代表一个对象变量不引用任何对象
引用对象后的对象变量
Myclass obj = new Myclass();
当对象不再使用时,JVM会回收Myclass对象所占用的内存,这个过程称为==“垃圾回收(GC:garbage collection)”==
理解赋值的含义
myclass obj1 = new myclass();
myclass obj2 = obj1;
独特的对象引用:this
- 对象中的所有数据字段都是通过this指针间接引用的
- 同一类中的方法可以相互调用,或者直接存取本类定义的字段,可以看成是其中隐含了一个this引用
作为常量的对象变量
- 可以使用final定义一个”常量“对象变量
final myclass obj = new Myclass();
- ⚠️:”常量“对象变量不能指向另一个对象,但可以修改对象所包容的数据,比如设置它所引用对象的某个公有字段值
对象判等
- 对于原始数据类型的变量(比如int),可以直接使用“==”判等来判断变量值是否相等
- 当“==”施加于原始数据类型变量时,是比较变量所保存的数据是否相等
- 当“==”施加于引用类型变量时,是比较这两个变量是否引用同一个对象
- 引用代表地址,所以“==”实际上相当于比较两个引用类型变量中保存的对象地址是否相同
比较两个对象相等的标准 - 两个对象的“内容”,其实是指它们在某一段时刻的所有字段的值,“内容相等”,其实就是”对应字段值“一致
- 方法:重写(override)基类的equals()方法
class myclass
{
public int value;
@Override
//这种写法说明此方法是“重写基类的同名方法”,Java中,这种以“@”打头的标记被称为“Annotation”
public boolean equals (Object obj)
{ return ((myclass) obj).value == this.value; }
//传入参数是Object类,所以这里有个类型转换
//这种override方式是简化的,在实际开发中不应该这样写,应该加上对参数有效性的检测代码
public myclass(int initvalue)
{value=this.initvalue;}
}
equals()方法从哪来?
- 当定义一个类时,如果不显示指明它的“父类”是哪个,默认时Object
也就是说当前自定义的类会继承Object类的所有方法 - Object是Java的最顶层基类,其中定义了equals()方法
object类的成员
自定义equals()方法
- 除了重写基类的equals方法,也可以“重载”(overload)equals()方法
class myclass{
public int value;
public boolean equals(myclass obj)
{
return obj.value == this.value;
}
//与前面“重写”方式的代码相比,equals()方法的参数类型是myclass而不是object
//并且方法本身也没有附加@override标记
public myclass(int initvalue)
{value = initvalue;}
}
类的构造方法
- consturctor构造方法/构造函数
- 当创建一个对象时,它的构造方法会自动调用
- 构造方法与类名相同,没有返回值
- 如果类没有定义构造函数,Java编译器在编译时会自动给它提供一个没有参数的“默认构造方法”
- 如果自定义构造方法之后,编译器提供的默认方法失效
- 多构造函数
同一个类可以有多个构造函数,多个构造函数之间通过参数来区分,这是方法重载的一个实例
同一个类的构造函数之间可以通过this关键字相互调用
class fruit{
int grams;
int calspergram;
fruit(){
this(55,10);
}
Fruit(int g,int c){
grams=g;
calspergram=c;
}
}
类的初始化块
- 可以在类中使用{}将语句包围起来,直接将其作为类的成员,成为类的初始化块
- 类的这种没有名字的成员,多用于初始化类的字段
- 在实际开发中应该尽量保证一个字段只初始化一次
类字段初始化顺序
- 执行类成员定义时指定的默认值或类的初始化块,由书写代码时哪个代码排在后面,就执行哪个
- 执行类的构造函数
- 类的初始化块不接收任何的参数,只要一创建类的对象,它们就会被执行。因此,适合于封装那些对象创建时必须执行的代码
- 当多个类之间有继承关系时,创建子类对象会导致父类初始化块的执行
//父类
class test
{
{
value=100;
System.out.println("这是父类的初始化块");
}
public int value;
public test(int value)
{
this.value=value;
}
public test()
{
System.out.println("这是父类的构造函数");
}
}
//子类
class subtest extends test
{
{
subvalue = 200;
System.out.println("这是子类的初始化块");
}
public int subvalue;
public subtest()
{
System.out.println("这是子类的构造函数");
}
}
//测试部分
public class welcome1{
public static void main(String args[]) {
subtest t = new subtest();
}
}
结果
这是父类的初始化块
这是父类的构造函数
这是子类的初始化块
这是子类的构造函数
类的静态字段
class test
{
static int value;
}
- 可以用对象名或类名作为前缀访问静态数据/方法(实际开发用类名)
test t = new test();
t.value=1;
test.value=1;
类的静态初始化块
- 可以使用”静态初始化块“初始化类的静态字段
public class test
{
public static int value;
static{
value=100;
}//类的静态初始化块
}
静态初始化块、初始化块、构造方法三者的顺序
class test
{
{
value=100;
System.out.println("这是父类的初始化块");
}
static
{
value1=200;
System.out.println("这是父类的静态初始化块");
}
public int value;
public static int value1;
public test(int value)
{
this.value=value;
}
public test()
{
System.out.println("这是父类的构造函数");
}
}
class subtest extends test
{
{
subvalue = 200;
System.out.println("这是子类的初始化块");
}
static {
subvalue1=200;
System.out.println("这是子类的静态初始化块");
}
public int subvalue;
public static int subvalue1;
public subtest()
{
System.out.println("这是子类的构造函数");
}
}
public class welcome1{
public static void main(String args[]) {
System.out.println("第一次调用:");
subtest t = new subtest();
System.out.println("第二次调用:");
subtest t1 = new subtest();
}
}
结果
第一次调用:
这是父类的静态初始化块
这是子类的静态初始化块
这是父类的初始化块
这是父类的构造函数
这是子类的初始化块
这是子类的构造函数
第二次调用:
这是父类的初始化块
这是父类的构造函数
这是子类的初始化块
这是子类的构造函数
总结
- 静态初始化块只执行一次,且父类的先执行,然后是子类的静态初始化块
- 然后是父类的初始化块、父类的构造函数
- 最后是子类的初始化块、子类的构造函数
- 创建子类对象时父类的静态初始化块、初始化块、构造函数都执行
类的静态方法
类的静态方法只能访问类的静态成员!!!!!!
class test{
int value;
static int value1;
static void clear(){
value1=0;
}//类的静态方法只能访问类的静态成员
}
An interesting question?
如何在静态方法里访问类的实例成员(即没有static关键字修饰的字段或方法)?
//自己做的时候在static里面new了一个对象,然后通过对象变量来访问非static成员
//其实也可以传参使用
//其实想到static出现的原因是因为有些函数本身存在的意义就是全局的,只不过是为了满足Java面向对象的需求,才不得不依附于类,所以就正常把它看成一个全局函数就好
//其实从public static void main()就可以看出来,在main函数里是可以随意创建对象的
class test
{
private int value;
public test(int i) {
value = 1;
}
public void showresult()
{
System.out.println("This is not static method!");
}
static void staticmethod()
{
System.out.println("This is a static method!");
test t = new test(1);
t.showresult();
t.value=2;
System.out.println("This is a static variable:"+t.value);
}
}
public class welcome1{
public static void main(String args[]) {
test.staticmethod();
}
}
static方法是在编译的时候确定的,在类装入内存的时候也装入内存了;非static方法是在实例化的时候装入内存的。
包装类
- 基本数据类型仅仅提供了数据,却缺少一些常用的功能,为此,Java提供了包装类
包装类=基本数据类型的数据+扩充的一些方法和字段 - 包装类是引用类型,可以创建相应的对象
使用包装类
Integer obj = new Integer(1);
int value = obj.intValue();
装箱和拆箱
BoxAndUnbox
int value = 100;
Integer obj = value;
int result = obj*2;
查看字节码的方法
- javap反汇编指令(终端)
反汇编工具javap的参数说明
- 使用反汇编工具jClasslib来分析生成的class文件
第一题:
class test
{
static int count=0;
static {
count=0;
}
public test()
{
count++;
}
static int returnCreateNumber()
{
return count;
}
}
public class welcome1{
public static void main(String args[]) {
test t = new test();
test t1= new test();
System.out.println(test.returnCreateNumber());
test t2 = new test();
System.out.println(test.returnCreateNumber());
test t3 = new test();
test t4= new test();
System.out.println(test.returnCreateNumber());
}
}