面向对象
Java面向对象的三大特征:封装、继承和多态,java提供private、protected和public三个访问控制符来实现封装,提供extends关键字让子类继承父类,子类继承父类会继承父类的Field和方法。构造器用于对实例进行初始化操作,构造器可以重载,如果多个构造器包含相同的代码,可以把这些相同的代码放到普通初始化块里完成。初始化代码块总在构造器前被调用。Java提供静态初始化块,用于初始化类,在类的初始化阶段被执行,只运行一次。
类和对象
Java中两个重要的概念:类(class)和对象(Object,实例,instance),类是某一类对象的抽象。对象是是一个存在的实体。Java语言中定义类的简单格式:
[ 修饰符 ] class 类名{
零到多个构造器
零到多个成员变量
零到多个方法
}
修饰符可以是public、final、abstract,或者完全省略。Java类名通常命名规则有一个或多个有意义的单词连起来,首字母大写其他小写,中间不能使用任何分隔符。Java没有严格命名规则,只要没有分隔符就行了。采用上面方式是为了提高可读性。
成员变量用来定义属于类或该类实例的数据状态,方法用于定义该类或该类实例的行为或者功能。构造器用于构造该类的实例,构造器只能通过new关键字来调用,返回该类的实例对象。注意:被static修饰的成员只能访问被static修饰的成员。被static修饰的成员是属于类的,不是属于实例的,类优先于成员存在,也就是说在被static修饰的成员存在内存之中,而没有被static修饰的成员,还没有存在于内存中,去访问一个不存在的成员,会报错。
注意:构造函数一般情况下不要用private修饰。
定义成员变量格式:
[修饰符] 成员类型 成员名 [= 默认值];
修饰符:修饰符可以省略,也可以使private,protected,public,static,final,其中public,protected,private只能出现一个,可以与static,final组合使用修饰成员变量。
成员类型:可以是是java语言允许的任何数据类型(基本类型和引用类型)。
成员名:命名规则和java类命名规则一致。
默认值:定义成员变量可以指定一个可选的默认值。
定义方法格式:
[修饰符] 返回值类型 方法名( 形参列表){
// 零到多条可执行语句组成的方法体
}
修饰符:修饰符可以省略,也可以使用private,protected,public,static,final,abstract其中public,protected,private只能出现一个,abstract 和final最多出现一个,可以与static组合使用修饰方法。
返回值类型:可以是是java语言允许的任何数据类型(基本类型和引用类型)。如果方法中声明了返回值类型,必须有一个有效的return语句。该语句返回一个变量或一个表达式,变量或表达式的类型必需和声明的返回值类型匹配。如果没有返回值,则必须使用void来声明没有返回值。
方法名:命名规则和java类命名规则一致。建议使用动词开头。
形参列表:形参列表用于定义该方法接受的参数,形参列表有零到多组“参数类型 形参名“组成,之间用英文逗号隔开,形参类型和形参名用空格隔开。
构造器格式:
[修饰符] 构造器名 (形参列表){
//零到多条可执行语句组成的构造器执行体
}
构造器是一个特殊的方法,定义构造器的格式和定义方法很像,少了一个返回值类型。
修饰符:修饰符可以省略,也可以是public,pirvate,protected其中之一。
构造器名:必须和类同名。
形参列表:和定义方法的形参列表一致。
如果给构造器定义了一个返回值,编译不会出错,但java会把这个假的构造器当成方法来处理。构造器返回值类型总是当前类,构造器不能有显示返回值,构造器返回值是隐式的。
有的类没有构造函数,系统将为他提供一个空参类型的构造器,如果程序显示提供构造器,则该默认构造器则会消失。
一般当用户显示提供有参构造器,建议提供一个无参构造器。
对象的产生和使用:
创建对象的根本途径是构造器,通过new 关键字来调用某个类的构造器,创建实力对象。如果访问权限允许,类定义的方法和Field可以通过类或者实例来调用。类.Field|方法,或者实例.Field|方法。
实例对象的内存分配:
假设定义Student类:
public class Student {
public String name;
public String ID;
Student(){}
public Student(String name, String ID) {
super();
this.name = name;
ID = ID;
}
public void study(){
System.out.println("学习");
}
}
有这么一段代码 Student s = new Strudent(“张三”,”011”);
这段代码会产生两个东西:一个是s变量,一个是Student对象,Student对象包含两个Field。
引用变量s存放在栈内存中,指向实际的Person对象。Person对象存放在堆内存中。而name和ID两个Field则会指向别的堆内存。而study()则会存放在方法区,如下图所示:
this关键字:
this关键字总是指向调用该方法的对象,根据this的出现的位置不同,this作为对象的默认引用有两种情况:
构造器中引用该构造器正在初始化的对象。在方法中引用调用该方法。
this关键字最大的作用即使让类中的一个方法,访问该类中的其他方法或Field。
对static修饰的方法,不能使用this关键字。
方法:
方法属性:
在java语言中方法不能脱离类或者对象而独立存在。如果需要定义方法,只能在类中定义方法,方法定义在类中,如果使用static修饰,则该方法属于类,否则属于该类的实例。
Java中一个类定义完成,只要不再重新编辑这个类文件,该类和该类的对象所拥有的方法是固定的,不会在改变。Java方法的属性:
1, 方法不能独立存在,只能在类中定义
2, 方法只属于类或者该类的实例
3, 执行方法时必须使用类或者对象作用调用者。被static修饰的方法可以使用类名和实例对象名来调用该方法。
方法参数传递:
Java中参数传递只有一种,就是值传递。就是将实际参数的副本传入进去,本身不受影响。
如下代码:
public class Test{
public static void main(String[] args)throws IOException
{
int a = -1;
int b = 1;
int[] arr = {1,2,3,4,5};
swap(a,b);
System.out.println("执行swap(a,b)后: a = "+a+"\t b = "+b);
swap(arr,1,2);
System.out.println("执行swap(arr,1,2)后:");
for(int i=0;i<arr.length;i++)System.out.println(arr[i]+" ");
}
public static void swap(int a,int b){
a = a^b;
b = a^b;
a = a^b;
System.out.println("a = "+a +"\tb = "+b);
}
public static void swap(int[] arr,int a,int b){
arr[a]=arr[a]^arr[b];
arr[b]=arr[a]^arr[b];
arr[a]=arr[a]^arr[b];
}
}
打印结果:
a = 1b = -1
执行swap(a,b)后: a = -1 b = 1
执行swap(arr,1,2)后:
1 3 2 4 5
Main函数中的a和b的值没有改变,但是其int数组arr发生了改变。首先我们要明白int数组是一个引用类型。引用类型的参数传递传送的是引用变量的地址值,是指向堆内存的,所以操作的是堆内存中的数据。
红色框中代表swap(arr,a,b)操作后的arr数组个元素的位置。
这个图例中可以解释为什么操作arr是,会改变arr元素位置,而操作a和b却不能改变main方法中a和b的值。
形参可变:
JDK1.5 以后java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的参数。在最后一个形参类型后面增加三个点,来表明该形参可以接受多个参数值,多个值被当成数组传入。
如下代码所示:
public class Test {
public static void main(String[] args) throws Exception {
print("itheima",1,2,4,5);
print("itheima",new int[]{1,2,4,5});
}
public static void print(String str,int ... arr){
System.out.println(str);
for(int s:arr){
System.out.print(s+" ");
}
System.out.println();
}
}
打印结果:
itheima
1 2 4 5
itheima
1 2 4 5
注意:长度可变的形参只能处于形参列表的最后。一个方法中只能有一个可变参数,这个可变长度的可以传入多个参数,也可以传入一个数组。
递归方法:
递归就是调用自身方法,有一个不是很恰当的例子即使,从前有个庙,庙里有个老和尚,老和尚对小和尚说,从前有个庙······
接触到最常见的就是求斐波那契数列。
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(FBNQ(4));
}
public static int FBNQ(int n){
if(n==1) return 1;
else if(n==2) return 1;
else return FBNQ(n-1)+ FBNQ(n-2);
}
}
注意:递归方法内部一定要有结束语句。否则会出现死循环。如果FBNQ方法中没有
if(n==1) return 1;
else if(n==2) return 1; 这两句话,则会出现死循环。从而得不到结果。
方法重载:
方法重载的要求:同一个类中,同一个方法名,参数列表不同。至于其他部分,则不作要求。举例说明:
public class Test {
public void test(){
System.out.println("无参数");
}
public int test(int a,int b){
return a+b;
}
public static void main(String[] args) throws Exception {
new Test().test();
System.out.println(new Test().test(3,5));
}
}
打印结果:
无参数
8
成员变量和局部变量
成员变量和局部变量的区别是成员变量是定义在类范围内的变量,局部变量是定义在方法内的变量。
成员变量:
成员变量分为类成员变量,就是被static修饰的,生命周期比较长,随着类的消失而消失。没有被static修饰的就是实例变量,实例变量的生命周期和实例一致。
访问成员变量的格式:
类成员变量: 类名.类成员变量名 或者 实例.类成员变量
实例成员变量: 实例.实例变量
何时使用static修饰成员变量?自己思考?省点事。
局部变量:
局部变量根据定义形式的不同,分为下列三种:
形参:定义方法签名时定义的变量,形参的作用域作用在整个方法内有效。
方法局部变量:在方法体内定义的变量,作用域从定义变量开始到方法结束时失效。
代码块局部变量:在代码块中定义变量,作用于从定义变量的地方生效,到代码块结束时失效。
局部变量除了形参之外,都必须显示初始化。
public class Test {
public static void main(String[] args) {
{
int a ;
a = 5;
System.out.println("代码块局部变量a的值:"+a);
}
//下面一句话访问 a 但是a不存在
//System.out.println(a);
//定义一个方法局部变量
int b ;
b = 5;
System.out.println("方法局部变量b的值:"+b);
}
}
成员变量和局部变量调用:
public class Test {
private String str = "itheima";
private static int lsh = 45;
public static void main(String[] args){
int lsh = 0;
// 打印输出 0
System.out.println(lsh);
//打印输出 45
System.out.println(Test.lsh);
new Test().show();
Test.show1();
}
public void show(){
String str = "lsh";
//打印输出 lsh
System.out.println(str);
//打印输出 itheima
System.out.println(this.str);
}
public static void show1(){
String str ="itcast";
String lsh = "hello";
// 打印输出“itcast hello”
System.out.println(str +" "+lsh);
// 打印输出 45
System.out.println(Test.lsh);
}
}
局部变量如果和成员变量出现同名的函数时,局部变量覆盖成员变量。可以通过指定类名和this关键字来作为调用者来访问被覆盖的成员变量。
变量的使用规则:
一、如果需要定义的变量是用于描述某个类或某个对象固有信息的,如果汽车的颜色,发动机类型,他是每个汽车对象固有的属性,这种变量应该定义成成员变量。如果汽车的有几个车轮这种需要定义成类成员变量。
二、如果某个类需要一个变量来保存该类或者实例运行的时的状态信息。例如银行排队取钱,排队的队列信息是实时变化的,而每个服务窗口需要不断获取队列信息。这种用于保存某个类或者实例状态信息的变量通常应该使用成员变量。
三、如果某个信息需要在某个类的方法之间共享,则应该使用成员变量。
成员变量作用域扩大到类存在的范围的害处:
增大了变量的生存时间,导致更大的内存开销;扩大变量的作用域不利于提供程序的内聚性;
构造器:
构造器最大的用处就是创建对象执行初始化操作。当创建一个对象时,系统会为对象的成员变量进行默认初始化,数值类型默认为0,booleen类型为false,引用类型为null。如果要改变这种默认初始化,可以通过自定义构造器来实现。
一旦程序中提供了默认构造器,系统就不再提供默认构造器。如果用户希望保留无参构造器,或者希望多个初始化过程,可以通过重载的方式来是实现。
构造器重载:
同一个类中具有多个构造器,多个构造器的形参列表不同,即成为构造器重载。和方法重载一致,只要求构造器的名字相同,形参列表不同。
public class Apple {
private String color;
private double weight;
private String version;
Apple(){}
Apple(String color,double weight){
//super 每个构造器都会隐式的存在 用来调用父类的构造器
super();
this.color = color;
this.weight = weight;
}
Apple(String color,double weight,String version){
//通过this关键字来调用第二个构造器
this(color,weight);
//通过this关键字应用该构造器正在初始化的java对象
this.version = version;
}
}
类继承:
Java的继承通过extends关键字来实现,实现继承的类被称为子类被继承的类成为父类。继承语法格式如下:
修饰符 class SubClass extends SuperClass{
//类定义部分
}
Java累继成只支持单继承,一个类只有一个直接父类。Object类没有父类。
重写和调用父类的方法:
子类继承了父类,也就是子类继承了父类全部信息,子类可以在父类的基础上 ,增加新的方法名和变量,子类需要重写父类的某些方法,重写方法的规则:方法名相同,形参列表相同,子类的返回值应该类型应该是父类的返回值或者父类返回值的子类,子类声明的抛出异常应该是父类抛出的异常类或者其子类。判断子类是否重写了父类的方法,可在子类的方法前加上@Override,如果编译器报错那么说明不是重写父类的方法。
子类覆盖父类方法后,如果子类的实例对象要调用该父类的方法,可以通过super或者父类名来作为调用者。如果父类的方法被private修饰,如果子类定义了一个与父类private方法有相同的方法名、形参列表、返回值类型,依然不是重写,因为子类不知道父类有这一方法。
子类中含有父类的同名变量:
如果子类包含有父类的同名变量,那么子类的实例对象访问父类中的该变量是时,应该通过super或者父类名作为调用者。如下代码:
class Person {
String name = "Person";
int age = 30;
public void show() {
System.out.println("Person类show方法");
}
}
class Student extends Person {
String name = "Student";
private int age = 45;
public void show() {
System.out.println("Student类show方法");
}
// 通过super调用父类的name实例变量
public void show_1() {
System.out.println(name + super.name);
}
}
public class Test {
public static void main(String[] args) {
Student std = new Student();
System.out.println(std.name);
// 调用Student实例的show方法
std.show();
// 向上转型调用Person的show方法
((Person) std).show();
// 不可访问Student的私有变量age
// System.out.println(std.age);
// 向上转型访问父类的age实例变量
System.out.println(((Person) std).age);
std.show_1();
}
}
如下面的图例:
调用父类构造器:
子类不会获取父类的构造器,但是子类构造器里可以调用父类构造器的初始化代码。在一个构造器中调用另一个重载构造器使用this调用来完成,子类构造器中调用父类构造器使用super调用完成。this和super使用起来很相似,一个调用本类中的重载构造器,一个调用父类的构造器。使用父类构造器必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现。
子类构造器总是会调用父类的构造器一次。有下面几种情况:
子类构造器执行体的第一行使用super显式调用父类构造器,系统根据super调用传入的实参列表调用父类对应的构造器。
子类构造器执行体第一行代码使用this显式调用本类重载构造器,系统将根据this调用里传入的实参列表调用本类中的另一个构造器。执行本类另一个构造器即会调用父类的构造器。
子类构造器执行体中没有super也没有this调用,系统将会在这行子类构造器之前,隐式调用父类的无参构造器。
class God{
public God(){
System.out.println("God 无参构造器!");
}
}
class Animal extends God{
private String name ;
private String think;
public Animal(){
System.out.println("Animal 无参构造器");
}
public Animal(String name ){
this.name = name;
System.out.println("Animal 一个参数构造器 ,动物名称为"+name);
}
public Animal(String name ,String think){
this(name);
this.think = think;
System.out.println("Animal 两个参数构造器,动物种类为"+think);
}
}
class Dog extends Animal{
public Dog(){
System.out.println("Dog 无参构造器");
}
public Dog(String name,String think){
super(name,think);
}
}
public class Test {
public static void main(String[] args) {
new Dog("汪汪","德国黑背");
new Dog();
}
}
打印结果:
God 无参构造器!
Animal 一个参数构造器 ,动物名称为汪汪
Animal 两个参数构造器,动物种类为德国黑背
God 无参构造器!
Dog 无参构造器
多态:
多态的产生是由于编译时类型和运行时类型不一致,就可能出现多态。
父类变量指向子类的引用,或者子类强转成父类。
class Fu{
String str = "itheima";
static int A = 5;
public void show(){
System.out.println("Fu"+"\t"+str);
}
public static void print(){
System.out.println("static Fu");
}
}
class Zi extends Fu{
int str = 1;
public void show(){
System.out.println("Zi"+"\t"+str);
}
public void show1(){
System.out.println("hehe");
}
public static void print(){
System.out.println("static Zi");
}
}
public class Test {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.str);
f.show();
f.print();
//f.show1(); 不能访问 Fu类不存在的方法
System.out.println(((Fu)new Zi()).str);
((Fu)new Zi()).show();
((Fu)new Zi()).print();
//((Fu)new Zi()).show1(); 不能访问
}
}
打印结果:
itheima
Zi1
static Fu
itheima
Zi1
static Fu
通过打印结果可以分析出来:
变量看编辑时期。
静态方法看编辑时期。
非静态方法看运行时期。
引用类型的强转只能在具有继承关系的两个类型之间进行。如果没有任何继承关系的类型,则无法进行类型转换,否则编辑时期就会出错。
初始化代码块:
初始化代码块语法格式:
[修饰符]{
//初始化块的可执行代码
}
class Person {
static {
number = 1;
// str = 5;
System.out.println("静态初始化块 1");
// System.out.println(number);
}
// {
// System.out.println("姓名:"+name+"\t"+"年龄:"+age);
// }
{
name = "win";
age = 45;
}
public static int number;
private String name = "lost";
private int age = 1;
{
System.out.println("姓名:" + name + "\t" + "年龄:" + age);
}
{
name = "win";
age = 45;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
{
System.out.println("姓名:" + name + "\t" + "年龄:" + age);
}
static {
System.out.println("静态初始化块 2");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Person.number);
new Person("张三", 6);
new Person("李四", 8);
}
}
打印结果:
静态初始化块 1
静态初始化块 2
5
姓名:lost年龄:1
姓名:win年龄:45
姓名:lost年龄:1
姓名:win年龄:45
静态初始化代码块,只执行一次。静态初始化代码可以对Person类中被static修饰的成员变量复制,但是不能访问。也可以在其代码块中定义局部变量并赋值。
普通初始化代码块每调用new 创建实例就会执行一次,可以对Person类中的实例变量进行赋值,但不能访问。只能在其定义变量的语句之后普通初始化代码块才可以方法实例变量。
初始代码块优先于构造器先执行。
有一个小小的疑惑?为什么初始化代码块只有在类变量或者实例变量定义之后才能用初始化代码访问,而为什么可以在类变量或者实例变量定义之前,给这些变量赋值。