类和对象的初步认识
定义类和对象
Java程序基本的单位是类(class)。
在面向对象中,类也是它的重要内容。
Java数据类型:基本数据类型;引用数据类型。
引用数据类型:String、Scanner……,他们都是预先写好的类,把他们当做数据类型来使用。一定意义上来说,类也可以称为引用类型。
String str =new String();
定义类的格式:
[修饰符] class 类名{
//定义属性
//定义方法
//定义构造器方法
}
练习:定义一个类Person,将这个类作为引用数据类型,再定义测试类,测试Person引用数据类型。
class Test
{
public static void main(String[] args){
Person p = new Person();
//设置属性
p.name = "zhangsan";
p.age = 18;
//调用方法
p.getInfo();
}
}
class Person
{
//定义属性
String name;
int age;
//定义方法
void getInfo(){
System.out.println("name"+name+"\n age"+age);
}
}
什么是对象?
对象,是一个具体存在的事物,他具有属性和行为(方法)。如:人,电脑,食物。
类和对象的关系:
类:说明书,图纸;
如果对象是建筑的话,类就是建筑的图纸;
对象是食物的话,类就是菜谱;
类就是用来描述对象的,描述对象具有什么属性,什么行为,类定义了对象。
类是:抽象的,对象就是具体的。
类与对象存在的关系:
对象是通过类创造的,那么类是优先于对象存在而存在。换句话说,类存在,对象不一定存在,但是对象存在了,类一定存在。
注意:“对象存在,类不存在”是不可能的,如果图纸都没有,那么怎么去盖房?
静态修饰符 static:
static只能修饰类的成员。
static 修饰变量:
static 类型 变量名;
static 返回值 方法名(){}
Static修饰的成员:是静态的,直接属于类,而不属于类创造出来的对象,随着类的创建而创建,随着类的消失而消失。
当类存在的时候,被static修饰的成员就一起存在了。
没有用static修饰的成员,当类存在的时候,这些成员并不存在。
这个时候,使用类名.静态成员是OK的,但是使用类名.非静态成员,是错误的,因为非静态成员并不是与类一起产生,那么使用一个存在的东西去访问一个不存在的东西,肯定是不行的!
怎么访问没有被static修饰的非静态成员?
可以通过new关键字创建该类对象,使用对象.非静态成员方式调用,没有被static修饰的非静态修饰的成员只属于创造出来的对象本身。
存在的顺序:类、静态成员优先于对象、非静态成员存在。
提示:按照存在顺序可以得出,静态成员只能调用、访问静态成员,不能访问非静态成员;非静态成员可以访问静态成员。
class Test { publicstatic void main(String[] args){ //静态成员是与对象一起存在,所以可以通过类名.静态成员方式调用 System.out.println(Animal.zhonglei); //cat是非静态成员,Aniaml调用这个时候还不存在的成员,会报错 System.out.println(Animal.cat); Animalani = new Animal(); //创建对象后,可以使用对象.非静态成员方式调用,也就是说,非静态成员属于对象 System.out.println(ani.cat); //使用对象.静态成员 不报错,因为静态成员优先于对象而存在。 System.out.println(ani.zhonglei); } } class Animal { staticString zhonglei = "动物"; Stringcat="猫"; } class Person { String a ="aa"; staticString b ="bb"; //验证静态成员调用非静态成员 //静态方法,调用非静态成员是错误的! /*staticvoid m1(){ a ="abc"; }*/
//验证非静态成员调用静态成员. void m2(){ b ="abc"; } }
直接类名调用成员和创建对象调用成员的区别:
类名调用成员,只能调用静态成员,不能调用非静态成员。
对象调用成员,既可以调用非静态成员,也可以调用静态成员,也就是说,非静态成员只属于它们的对象。
对象与对象之间的关系与区别:
使用同一个类Dog创建出来的对象,是两个具体存在的事物,不是同一个,他们虽然类型和属性相同,但是互不影响,他们都是独立存在的个体。
N个对象与静态成员之间的关系:
静态成员直接属于类,是唯一的。对象也可以直接访问静态成员,所以说,N多个该类的实例对象访问的静态成员其实是同一个,换句话说,静态成员被N多个实例对象共享。
特殊的成员:构造器方法
构造器方法:是用来构造对象的,当我们使用new关键字创建对象的时候,这个创建过程是一个行为、动作。其实他也是一个方法,只不过我们以前是没有注意过这个方法。
定义格式:
public 类名([参数类型 参数名]){
//代码
}
使用方式:
构造器方法,当我们使用new关键字创建对象的时候调用。
new Animal();//这个时候,实际上是调用了对象的构造器方法。如果我们在定义对象的时候,没有写构造器方法,就会隐式的存在一个无参的构造器方法。 class Animal { staticString zhonglei = "动物"; Stringcat; Stringage; }
当我们定义了构造器方法,那么这个无参构造器就消失了。
class Animal { publicAnimal(String a){ age =a; } staticString zhonglei = "动物"; Stringcat; Stringage; }
当我们定义了构造器,并且指定了构造器需要的传参参数,之前隐式的空构造器方法就会消失。
对象创建后,对象的成员的默认值:
Int:0
Byte:0
Long :0
Short :0
Double:0.0
Float:0.0
Char:’’//编码表里面编码为0的那个字符
Boolean:false
引用数据类型:null
对象的引用和生命周期:
对象的创建:
使用new关键字创建对象,在堆空间中,开辟一块区域用来存放这个对象的数据,然后将这个对象的门牌号(地址值)赋值给栈空间中该类型变量。
引用:栈空间中的变量指向堆空间中的对象。
用手机号绑定QQ账号,使用手机号登陆QQ和使用QQ号登陆QQ,登陆的是同一个账户。那么使用手机号登陆QQ后,发了一条朋友圈,再使用QQ账号登陆,那条朋友圈信息还在。
栈空间中的变量可以有N个同时指向堆空间中的同一个对象。那么使用这几个引用变量去访问数据,其实访问的是同一个数据。
//dog1存储的是指向dog对象的地址 Dogdog1 = new Dog(); dog1.name= "哈士奇"; dog1.age= 3; Dogdog1_1 = dog1; System.out.println(dog1_1.name); dog1_1.name= "泰迪"; System.out.println(dog1.name);
分析:定义dog1指向一个对象,将他存储的地址赋值给dog1_1,那么使用dog1_1和dog1去访问、操作的其实是同一个对象。
对象的消失:
当没有引用(变量)指向对象时候,对象无法被访问和操作了。那么在堆空间中的对象就是垃圾。
Java提供一个垃圾回收机制,GC,通过该线程不定时的回收垃圾(没有引用指向的对象)。
//对象没用了,那么怎么回收对象所占用的内存空间 //将引用指向对象的连接中断就可以了。 dog1 =null; //System.out.println(dog1.name);空指针异常 dog1_1=null; /*这个时候,已经没有任何引用指向之前定义的对象 那么之前的那个对象就是垃圾了,java的GC会不定时 清理垃圾 */
构造器:
用来创建对象,使用new关键字创建对象,实际上就是调用该对象的构造器方法。
构造器方法与普通方法区别:
1、 没有返回值,不能使用return
2、 调用方式是使用new关键字调用,而方法是通过 类名.方法名调用。
3、如果没有定义构造器方法,那么java会有一个隐藏的无参构造器,如果定义了构造器方法,那么该隐藏的无参构造器就消失。
构造器除了用来创建对象,还可以做一些对象初始化动作。
class Test { publicstatic void main(String[] args){ Dogdog1 = new Dog(); System.out.println(dog1.name); } } class Dog { //在创建Dog类型对象时,对象的属性就有初始值。 publicDog(){ name ="哈士奇"; } staticString aixinjiayuan = "爱心家园"; Stringname; } 还可以传参给构造器,用来初始化数据。 class Test { publicstatic void main(String[] args){ Dogdog1 = new Dog("金毛"); System.out.println(dog1.name); Dogdog2 = new Dog("哈士奇"); System.out.println(dog2.name); } } class Dog { //在创建Dog类型对象时,对象的属性就有初始值。 publicDog(String str){ name =str; } staticString aixinjiayuan = "爱心家园"; Stringname; }
this 关键字:
代表所在的类中创建出来的对象本身。
class Dog { //在创建Dog类型对象时,对象的属性就有初始值。 //遇到局部变量与外部变量重名,可以使用this找到外部变量。 //this代表当前创建的对象本身。 publicDog(String name){ this.name= name; } staticString aixinjiayuan = "爱心家园"; Stringname; }
方法的定义:具有一定功能的代码块,有返回值,也可以传参。
定义格式:
[修饰符] 返回值方法名([参数类型参数名称]){
//0到多行代码块
}
方法的重载:
在一个类里面,可以定义n多个同名方法,只需要参数列表不同即可。
重载只与形参列表有关,与返回值、修饰符无任何关系。
publicstatic int add(int a , int b){ returna+b ; } publicstatic int add(int a , int b, int c){ returna+b+c ; } publicstatic int add(int a , int b, int c,int d){ returna+b+c+d ; } 构造器是特殊的方法,那么构造器也可以重载: publicDog(String name){ this.name= name; } publicDog(String name ,int age){ this.name= name; this.age= age; } staticString aixinjiayuan = "爱心家园"; Stringname; int age;
参数列表的可变长参数:
当我们遇到需要传递同一种参数类型的数据,但是参数个数较多,这个时候如果依次重载不同参数列表的方法,就显得代码臃肿。
如:遇到如下情况,就需要重载4个add方法。
result=AddMethod.add(1,2); System.out.println(result); result=AddMethod.add(1,2,3); System.out.println(result); result=AddMethod.add(1,2,3,4); System.out.println(result); result=AddMethod.add(1,2,3,4,5);
解决这个问题,可以使用可变参数。
作用:接收同一种数据类型的数据,但是接收的参数个数是可以变化的。
接收过来以后,java自动将接收的数据封装到数组里面,在方法的代码块中,这个参数就是一个该类型的数组。我们将它当成普通数组使用即可。
问题:数组是一个不可改变长度的容器,为什么在使用的时候,可以传递不同长度的数据?
这里的可变参数数组的定义,是根据传递过来的参数来定义。
定义格式:
[修饰符] 返回值类型方法名(参数类型…参数名){
//参数名就是一个该参数类型的数组。
}
注意:可变参数,放置的位置只能是在方法参数列表的最后一个参数的位置。并且,一个方法参数列表中只能有一个可变参数。
publicstatic int add(String str, int ...arr){ intresult =0; for(inti = 0;i < arr.length ; i++){ result+=arr[i]; } returnresult; }
特殊的方法调用:递归
递归:自己调用自己。方法内部代码调用方法本身。
class Test2{ static int method(int i){ if(i ==1){ returni ; } int result= method(i - 1); returnresult; } public static void main(String[] args){ int result = method(5); System.out.println(result); } }
案例:阶乘
阶乘:5的阶乘是 1*2*3*4*5 6的阶乘是1*2*3*4*5*6 从1到它本身的整数相乘。
使用for循环求阶乘:
使用递归算法求阶乘:
class JieCheng{ public static void main(String[] args){ int result = jiecheng(3); System.out.println(result); } public static int jiecheng(int num){ if(num ==1){ returnnum; } num = num * jiecheng(num -1) ; return num; } }
成员变量和局部变量:
成员变量:在成员位置,属于对象或者是类。随着类或对象的创建而创建,消失而消失。
局部变量:定义在方法中,随着方法的创建而创建,随着方法的消失而消失。
//成员变量 Stringchengyuan; //成员方法 publicvoid method(){ //局部变量 Stringjubu; }
形参:定义在方法参数列表中,表示一个参数的类型和参数名。
实参:方法执行时候,传递进来的实际参数(值)。
变量在内存中的生命周期:
类变量:与类同时创建,与类同时消失。
实例变量:创建对象时,创建实例变量,对象被回收,属于该对象的实例变量消失。
局部变量:方法被调用,局部变量被创建,方法执行完成,局部变量被回收。
代码块
代码块:执行一段代码,没有名称,没有返回值,没有参数,不能被调用。
静态代码块:类存在了(类加载到内存当中),执行静态代码块中的代码,并且只会执行一次。
class Test
{
//类加载到内存中就执行该代码块
static {
System.out.println("静态代码块执行了");
}
publicstatic void main(String[] args){
}
}
第二种情况:
class Test
{
publicstatic void main(String[] args){
Dog dog1 =new Dog();
}
}
class Dog{
//类加载到内存中就执行该代码块
static {
System.out.println("静态代码块执行了");
}
}
构造代码块:对象创建(new Dog()),执行一遍构造代码块,并且只会执行一次。
class Test
{
publicstatic void main(String[] args){
Dog dog1 =new Dog();
Dog dog2 =new Dog();
}
}
class Dog{
//对象一旦创建就执行该代码块
{
System.out.println("普通代码块执行了");
}
}
代码块的作用:就是初始化对象、初始化类时候做一个初始化动作,如:赋值操作。
类、对象产生的顺序及在内存中的存储:
类加载到内存,通过类名调用静态方法,如:类名.静态方法;通过类创建对象,new 对象()。
对象加载到内存,就是new关键字创建后加载到内存。
静态成员、静态代码块、实例变量、构造代码块、构造方法
以new对象为例,说明类和对象中数据以及代码块执行的顺序:
class Test
{
publicstatic void main(String[] args){
Dog dog1 =new Dog();
//当我执行new Dog()会发生了什么事情?
/*
1、加载类到静态区域
2、先创建静态成员
3、执行静态代码块
4、创建实例变量
5、构造代码块
6、构造方法
*/
}
}
class Dog{
//如果不给静态成员初始化值,会有默认初始值
staticString aixinjiayuan = "abc";
Stringname = "泰迪";
staticvoid jingtai(){
intnum = 0;
}
voidshili(){
Stringstr = "";
}
//静态代码块
static {
System.out.println(aixinjiayuan);
System.out.println("普通代码块执行了");
}
//对象一旦创建就执行该代码块
{
System.out.println(name);
System.out.println("普通代码块执行了");
}
publicDog(){
System.out.println("构造方法执行了");
}
}