Java笔记:Java面向对象
一、方法 1、概述 方法,也可以称之为函数,在其他语言中可能方法和函数的概念和语法是不同的,比如Python中的函数是可以在文件中独立定义并存在的,而方法则是在类之中定义的函数,但是在Java中,方法和函数都指的是同一个语法,都是一样的,既可以称它为方法,也可以称它为函数。需要注意以下几点:
2、定义方法 语法如下: [修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}
? 修饰符列表:这是可选项,不是必须的。如果不写,则使用默认选项,对于访问控制符,如public、private等,缺省的访问控制权限为包范围内。 返回值类型:使用“return 值”返回一个值,且这个值的类型必须和指定的返回值类型一致,指定的返回值类型可以是Java中的任何数据类型,包括基本数据类型和所有引用类型。如果此方法不返回任何值,就必须将其指定为void,表示此方法不返回任何值,即方法体中不能有“return 值”这样的语句,但是可以写“return;”表示返回void。 方法名:遵循标识符的定义规则,但通常应该注意以下几点:
形式参数列表:
方法调用:使用点号“.”进行调用,但是注意,方法体中的程序只有在调用时才会去执行,定义或编译时都不会执行。 3、static方法调用 如果修饰符列表中有static的话,则称之为静态方法,调用此方法的语法格式为“类名.方法名(实际参数列表)”,但如果是调用本类中的static方法,则可以省略类名,直接使用方法名进行调用。 4、参数值的传递 在调用方法时,给方法传递的参数为变量的值(即值传递),而不是变量本身,因为如果传递的是变量本身,那岂不是就可以在调用的方法中使用这个变量了,但实际情况却是,调用的方法只能使用自己这个作用域中的局部变量,而不能使用它的调用者所在的作用域中的变量。所以传递的就是变量的值,从而使得方法的形参有了自己的值。 5、可变长参数 方法的可变长参数指的是定义方法时只需要定义参数的类型,而不用写死这种类型的参数个数,使用时也可以根据需要传入不同的参数个数。 public class Test{ static void main(String[] args){ // 使用时,可传入0-n个参数 func(); func(10); func(10,20,30); } 语法格式:定义方法的参数时,使用形如“类型... 参数名称”的格式,注意,类型名称之后一定是三个点 在方法中使用这个参数时,可以将这个可变长参数当做数组来处理 void func(int... args){ code... } 可变长参数的定义必须也只能是参数列表的最后一个位置,且一个方法只能有一个可变长参数 void func(String s, } } ? 6、方法重载(overload) 重载原则:重载的方法功能相似的时候可以考虑使用方法重载,但是功能不同的时候尽量使用方法名称不同的方法来定义。 重载机制:
7、JVM内存分配 如果只是定义方法,不去调用,则不会被执行,在JVM中也不会给该方法分配运行所需的内存,只有在调用这个方法的时候才会动态的分配该方法所需的内存空间。
8、方法递归
?
一个普通的类的定义语法如下: [修饰符列表] 类名{
属性;
方法;
}
成员变量:在类体之中、方法之外的变量称之为成员变量。成员变量如果没有手动赋值的话,系统会自动赋予默认值(一切向“0”看齐)。成员变量又分为:
注意:类也是一种数据类型,属于引用数据类型,它的类型名称就是对应的类名。 2、对象创建和内存分配 对象就是类实例化之后的具体个体,类到对象的过程称之为实例化,反过来,对象到类的过程则称之为抽象。 new关键字:Java中使用new关键字来创建一个对象,new关键字也是Java中的一个运算符。 内存分配:当在方法区内存中的代码执行时,会在栈内存中开辟一块该方法对应的内存空间,而在方法执行过程中使用new关键字创建一个对象时,则会在堆内存中开辟一块该对象对应的内存空间。所以方法中定义的局部变量是在栈中的,而创建的对象则是在堆内存中的。实例对象每一个对象都会有自己的一块内存空间,即100个对象就会分配100个内存空间。 指针屏蔽:Java中想要访问堆内存中的数据,必须通过引用,而不能直接操作堆内存,因为Java中屏蔽了指针的概念,不能通过指针的方式直接访问或操作内存中的数据。 访问属性:对于实例变量属性的读取和修改,使用语法格式“引用.变量名”进行读取,使用语法格式“引用.变量名=值”对属性进行修改。注意,实例变量存储在堆内存中对应的实例对象内部,且不能通过类名的方式来访问。 3、空指针异常NullPointerException 当一个引用类型的变量的值不再是指向某个对象的内存地址,而是null,此时再去访问对象的相关属性或方法就会发生空指针异常,因为此时的变量不再指向该对象,而是值为null了,无法去访问该对象了,更不要说访问对象中的属性和方法了,空引用访问实例相关数据就一定会出现空指针异常。 4、get方法和set方法 属性私有化:在封装特性中,类中的所有属性都应该使用private修饰符进行修饰,private表示私有的,表示此属性只有在本类中才能访问,在类的外部不能访问。但是在类中应该为外部访问这些属性提供一些简单的公开的(public)操作入口,如对应的get方法和set方法。 get方法和set方法的写法如下: get方法 public 返回值类型 get+属性名首字母大写(){ return 属性名; } set方法 注意,形参的名字如果和属性名相同了,那么属性名前面应该加一个this关键字 因为不加this关键字的话,由于名称是相同的,Java的就近原则会认为它俩都是同一个局部变量,即形参 void set+属性名首字母大写(形参列表){ 属性名=形参值; ... } ?示例: A{ private age; getAge(){ 这里可以使用this.age,也可以不用this return this.age age; } void setAge( age){ 这里因为形参和属性名相同了,所以必须用this加以区分 this.age = age; } } ? 注意:get和set方法是没有static修饰符的,使用的是public修饰符,没有static修饰符的方法的访问方式为“引用.方法名(实参)”。 5、引用参数的传递 对象变量通常也称之为引用,因为在栈中这个变量只是个局部变量,而对象变量的值是该对象在堆内存中的内存地址,当然,这个内存地址则指向堆内存中的该对象实例。所以对于基本数据类型,值的传递不会影响到原本变量的值,但是对于类的实例,因为传递的值是内存地址,所以它虽然不会影响原本局部变量的值(即内存地址),但是如果对内存地址中的对象实例进行修改则会影响到内存地址指向的实例对象,即原本的局部变量指向的实例对象会被修改。 6、构造方法(constructor) 语法如下: 构造方法,也称为构造器(constructor)。 构造方法是不用也不能指定返回值类型的。 注意,构造方法名必须和类名相同,所以这里的语法就直接写类名了。 [修饰符列表] 类名(形式参数列表){ 构造方法体; } ?示例: i; 下面的两个构造方法使用了方法的重载机制 public A(){ System.out.println("类A的无参构造方法!"); } public A( i){ 使用this关键字区分实例变量和方法的局部变量 this.i = i; System.out.println("类A的有参构造方法!"); } } ? 构造方法的调用:构造方法的作用是通过调用构造方法来创建对象并初始化实例变量的值,而构造方法的调用使用new关键字“new 构造方法名(实参列表)”,注意new之后调用的其实是构造方法名而不是类名,但因为两者是相同的,所以可能会让人误以为调用的是类名。 构造方法返回值:虽然没有指定返回值类型,但是构造方法的返回值类型就是其所在类的类型,返回值就是新创建的对象的引用,但是注意的是这个返回值是不需要开发人员手动编写的,即构造方法的定义中,返回值类型和返回值都不需要人为的去定义。 默认构造方法:当类中没有定义构造方法时,系统会给该类提供一个无参数的默认构造器。需要特别注意的是,如果类中提供了构造方法,那么系统就不再为这个类提供默认的无参数构造方法了,所以,如果在类中提供了自己的构造方法,那么推荐手动将无参的构造方法加上,因为这个构造方法太常用了。 关于构造方法,还应该注意以下几点:
7、this关键字 其实每一个实例对象中都有一个this变量,this中保存的是自身所在实例对象的内存地址,即this是指向实例对象本身的一个引用类型的变量。可以换一种方式理解,this可以出现在实例方法中,而方法中的this代表当前正在执行这个方法动作的实例对象。 在实例方法中对实例变量的访问,由于它是实例变量,所以不使用this关键字也是可以访问的,所以this在多数情况下是可以不写的。this主要用于区分实例变量和局部变量,比如setter方法和构造方法中就比较常用。 当然,this不能在含有static修饰符的方法中使用。 this关键字除了使用“this.xxx”的方式表示实例对象的使用之外,还可以在构造方法中以“this(实参列表)”形式表示调用本类的另一个构造方法,但是注意,使用这种用法时这个语句只能出现在构造方法的第一行(当然这个语句之后可以添加其他的语句,但前面就不能有其他任何语句了),如: User{ age; public User( age){ age; } User(){ 此处表示调用另一个构造方法 但是注意,这个语句只能是此构造方法的第一个语句 this(18); 之后可以加别的语句 System.out.println("my age is " + this.age); } } ?8、super关键字 super关键字和this关键字在用法上有许多相似的地方,但是this代表的是当前实例对象,而super代表的是当前子类的父类的特征(包括属性和方法),通常用于访问父类的某些属性和方法。和this对比着看,它们的相似之处如下:
对于super()这种用法,当一个子类的构造方法的第一行既没有this(),也没有super(),那么默认会有一个super()执行,表示在子类的构造方法中调用父类的无参构造方法,此时必须保证父类必须有一个无参构造方法,推荐在类的定义中都手动写好一个无参的构造方法。当然,要是你自己手动调用了this(实参列表)或者super(实参列表),程序就会按照你写进行调用了。示例如下: TestSuper{ 执行结果: 类A的无参构造方法! 类B的无参构造方法! new B(); } } ); } } class B extends B(){ 由main方法的输出可以看出类A的无参构造方法也是被执行的, 其实如果没有手动调用super(),此处会默认执行一个super() super(); System.out.println("类B的无参构造方法!"); } } 关于super的使用,注意以下几点:
9、继承 继承特性优点:继承最基本的作用是代码复用,但是最重要的作用却是有了继承才有了方法的覆盖和多态机制。 单继承:Java中的继承机制只支持单继承,一个类不能同时继承多个类,只能继承一个类。语法如下: 继承使用extends关键字 [修饰符列表] class 类名 父类名{ 类体; } 可以继承的数据:
多继承:Java中虽然只支持单继承,但是可以间接实现多继承: C B{ } B A{ } A T{ } 这样C直接继承B,但间接继承了T和A类 ? 默认基类:Java中一个类如果没有显式继承任何类,那么该类默认继承javaSE库中提供的java.lang.Object类。 需要注意一个概念,当一个子类在继承某个父类时,在运行时,不是说在子类中查找对应方法或属性,子类中没有再到父类中查找,而是在定义时,如果继承了某个父类,那么这个类的定义中就包含了父类继承过来的某些方法和属性,即子类对象执行的方法和属性总是自己的属性和方法。 10、方法的覆盖/重写(override) 方法的覆盖也称为方法的重写,子类将父类继承过来的方法进行重新编写被称为方法的重写,方法重写时需要注意:
11、多态 向上转型(upcasting):子类型 --> 父类型,可以理解为自动类型转换。 多态语法机制:父类型的引用指向子类型对象这种机制导致程序在编译阶段和运行阶段出现了两种不同的形态或状态,这种机制可以称为一种多态语法机制。 多态的作用:降低程序的耦合度,提高程序的扩展力。能使用多态就多使用多态,即父类型引用指向子类型对象。 示例:重点在注释哦 public Animal{ run(){ System.out.println("动物在移动!"); } } class Cat run(){ System.out.println("猫在散步!"); } catchMouse(){ System.out.println("猫在抓老鼠!"); } } class Bird Animal{ run(){ System.out.println("鸟儿在飞翔!"); } } Test{ main(String[] args){ 此处为向上转型,从Cat类型自动转换为Animal类型 Animal cat1 = Cat(); 向上转型之后,可以访问父类型中的方法,但是如果这个方法被子类型中重写了 那么执行的就是子类型中的方法了,并且类型转化之后不能再执行子类型中特有的方法了 比如catchMouse方法,但是需要注意的是,虽然类型转换了,但是引用指向的堆内存中的 对象依然是最开始创建的Cat类型的源对象cat1,所以执行方法时原则就是子类型中没有就执行继承自父类型的方法,如果子类型中有这个方法时就执行子类型中的方法,但是不能执行子类型中特有的方法。 在编译阶段会将符合语法的该对象的方法绑定,这个过程称之为静态绑定,只有静态绑定成功之后才能运行程序。这个例子中,静态绑定是将Animal的run方法绑定到cat1对象,因为cat1是声明为Animal类型的,而Animal类是有run方法的,所以能绑定成功。 在运行阶段则会将实际运行的方法绑定到该对象上,这个过程称之为动态绑定,这个例子中,动态绑定是,在运行时,由于是先在内存中生成的对象是new出来的Cat类型的对象,虽然在等号赋值运算时类型被转换为Animal类型了,但是内存中其实还是那个被创建好的Cat类型的cat1对象,所以会执行Cat类中的run方法。 cat1.run(); 输出为:猫在散步! 此处会编译不通过,虽然cat对象有catchMouse方法,但是类型转换后,因为Animal类型中没有catchMouse方法,所以编译不通过,即静态绑定失败。当然,也就不可能继续运行了。 cat1.catchMouse(); 向下转型,这里不仅能编译通过,还能正确执行catchMouse方法,因为cat1其本质就是最初在内存中创建的Cat类型对象,而Cat类是由这个方法的 Cat cat2 = (Cat)cat1; cat2.catchMouse(); 此处的向下转型编译能能通过,但是运行会报错java.lang.ClassCastException(除了空指针异常之外另一个著名的异常),即类型转换异常,而且只有在向下转型的时候会发生。 因为第一个语句向上转型后,其实际还是个Bird类型对象,在第二个语句的向下转型,因为 Animal类型和Cat类型之间具有继承关系,所以可以编译通过,但是运行时由于它本质是Bird类型 对象,不能转换成Cat类型对象,因为Bird和Cat之间没有继承关系,所以会报错。 Animal bird1 = Bird(); Cat cat3 = (Cat)bird1; } } ? 12、instanceof运算符 语法:“引用 instanceof 数据类型名”,返回值为true/false,true表示这个引用指向的内存真实对象就是该数据类型的对象,false则表示这个引用指向的内存真实对象不是该数据类型的对象。如上例中“Animal bird1 = new Bird();”的bird1虽然转换成了Animal类型,但其真实内存对象其实是Bird类型的,所以如果执行“bird1 isinstanceof Bird”就会返回true。 13、抽象类 抽象类使用abstract关键字修饰,是类和类之间共同特征的提取而形成的类,通常抽象类中含有抽象方法,但也不是说抽象类中就一定需要定义抽象方法。对于抽象方法的定义,需要注意,它同样需要abstract关键字修饰,同时不能有大括号,还需要以分号结尾。 抽象类也属于一种引用类型,使用抽象类来定义一个子类的对象,这种语法正是多态的应用,即向上转型,父类型的引用指向子类型的对象。 语法 [修饰符列表] abstract 类名{ 通常含有抽象方法,但也不是必须的 类体; } ? 示例: 抽象类使用abstract关键字修饰 Animal{ 抽象方法也使用abstract关键字修饰 并且抽象方法定义时不能有大括号 run(); } class Dog 如果子类继承自抽象类,但自身又不是抽象类 那么子类就必须重写/覆盖/实现抽象类中的所有抽象方法 run(){ System.out.println("小狗在奔跑!!!"); } } ? 使用抽象类时,应注意以下几点:
14、接口 接口在学习时候虽然可以将它当做是类来理解,但是注意,接口并不是类,定义使用的关键字是interface而不是class,但是编译之后也是一个class字节码文件。 同时,和抽象类一样,接口也是一种引用类型,使用接口的时候,可以使用多态,或者说接口的使用离不开多态,因为接口本身无法直接创建对象,一旦创建对象就必然是接口的“子类”,即向上转型,父类型的引用指向子类型的对象。 interface 接口名{ 常量或抽象方法; } ? 示例: HelloWorld{ 这里是向上转型 A a = C(); a.func1(); 这里是向下转型,接口的向下转型编译不会报错,但是运行的时候可能报ClassCastException异常 所以无论是类之间的向下转型还是接口之间的向下转型,转型之前,都应该使用instanceof运算符判断下 当然,这里是不会报错的,因为a对象本质上是个C对象,而C中有实现了B,所以转成B是没有问题的。 B b = (B)a; b.func2(); 输出结果: func1... func2... } } 接口中只能定义常量和抽象方法,并且都只能是public 接口中的常量都只能是public static final修饰的,这三个关键字也是可以省略不写的,编译的时候会自动加上的。 int i = 10; 接口中的抽象方法都只能是public abstract修饰的,这两个关键字是可以省略不写的,编译的时候会自动加上的。 func1(); } B{ func2(); } 类实现接口使用implements,而不是extends 一个类可同时实现多个接口,类对接口的多实现相当于多继承,这其实弥补了Java的类和类之间只能单继承的缺点。 如果实现接口的类不是抽象类,那么就必须实现接口中的所有方法 class C implements A,B{ func1(){ System.out.println("func1..." func2(){ System.out.println("func2..."); } } ? 使用接口的使用,需要注意以下几点:
15、抽象类和接口的区别 其实,在实际使用中还是接口使用的多,抽象类使用的少。抽象类和接口看着有许多相似的地方,但它们之间的区别还是有很多的,如下:
? 三、修饰符 1、static 静态变量:带有static关键字的变量,称之为静态变量,并且,在类加载的时候就静态变量开始初始化了,不需要创建对象它的内存就已经开辟了,并且是存储在方法区中的。 静态方法:带有tatic修饰符的方法称为静态方法,在静态方法中不能访问实例变量和实例方法,当然,也包括this关键字,而是只能访问同样带有static修饰符的变量(静态变量)。 使用原则:当一个方法或变量它的执行与具体的对象无关,或者说所有对象都会用到这个方法或变量,并且还不会因为对象的不同而发生变化,此时就应该将它定义为static类型。而当一个行为或动作执行的过程中需要对象参与,或者说不同对象执行这个动作的结果可能会不同,那么这个方法就应该定义为实例方法,不应该加static关键字。同理,当一个属性在不同的对象中可能会不同时,那这个属性就应该定义为实例变量,也不应该加static关键字。 访问static变量和方法:带有static的方法和变量,可以使用类名的方式去访问,也可以使用引用的方式去访问,但使用引用的方式去访问,其实本质上也是使用类名的方式去访问的,因为你会发现当这个引用为null时也能去访问static的方法和变量,而不会报空指针异常,所以不推荐使用引用的方式去使用static方法。 static另一种语法的使用:静态代码块,在类加载的时候就会去执行这个方法,定义和使用示例如下: 语法 static{ java语句; } 静态代码块在一个类中可以编写多个 ? StaticTest{ { System.out.println("--->1"); } { System.out.println("--->2" main(String[] args){ System.out.println("main method!!!"); } } 运行结果为:可以看到静态代码块在main方法之前运行了。 --->1 --->2 main method!!! ? 2、final final关键字的使用,需要注意以下几点:
实例变量如果声明为final变量,那么在声明的同时就需要给它赋值,不然就会报错。因为类的实例变量在调用构造方法之后还没有被赋值的话就会被系统赋予默认值,而final变量是不能重新赋值的,所以如果允许final实例变量声明的时候不赋值,那么这个变量将永远是系统默认的值,这样肯定是不行的,所以语法上就要求final的实例变量必须在声明的同时必须手动赋值或者在构造方法中给它赋值。示例如下: A{ 第一种方式:声明的同时赋值 final int a = 10; 第二种方式:先声明,然后在构造方法中赋值 b; A(){ this.b = 20; } 注:其实这两种方式都是一种方式,都是在构造方法执行过程中给实例变量赋值的。 } ? 注意,final修饰的引用虽然不能再指向其他对象,但是所指向的对象内部的内存是可以被修改的。 final修饰的实例变量通常会和static联合使用,被称为常量。 3、常量 语法格式:“public static final 类型 常量名 = 值”。final表示不可被修改,static表示无论实例化多少对象都只会保存一份数据在方法区内存中,此时,就算被声明为public也不用担心被别人修改,因为它本身就是final不可被修改的。 4、访问控制权限修饰符 对于属性和方法,以下4种都可以使用,但是对于类,只可以使用public和缺省的方式定义,但是无论是类还是属性和方法,这4中的作用范围都是相同的:
? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |