1. 面向对象思想
1.1类与对象的关系
类与对象的关系:
类是对某一类事物的描述,是抽象的,概念上的定义。对象是实际存在的该类事物的个体。
如图:类(树叶)是一个抽象的概念,它是从所有对象(各片不同的树叶)提取出来的共同特征描述,而对象(各片具体的不同树叶)则是类的实例化。
1.2 类与对象内存解析
示例一:定义一个Car类
//定义一个Car类,描述车
class Car
{
//定义汽车的属性:轮胎数,颜色
int num;
String color;
//运行
void run()
{
System.out.println(num+";"+color);
}
}
public class CarDemo
{
public static void main(String[] args)
{
//创建对象
Car c = new Car();
c.num = 4;
c.color = "red";
c.run();
}
}
1.
如图:在数组中有介绍过,数组是一种引用数据类型,类也是一种引用数据类型,因此主函数中定义的一个Car类型的变量c
实际上是一个引用,它被存放在栈内存中指向实际的Car对象,而真正的Car对象存放在堆内存中。
2.
Car c1 = new Car();
Car c2 = new Car();
c1.num=5;
c2.color="blue";
c1.run();
定义了两个Person类对象,栈内存中存放引用变量c1和c2,c1和c2分别存放两个对象的地址值,在堆内存中开辟两片内存空间,通过引用变量指向对象,有几个new就会开辟几个内存空间。
3.
把c1变量的值赋给c2变量,也就是把c1变量保存的地址值赋给c2变量,这样c2变量和c1变量将指向同一个Car对象。
示例二:定义一个Person类
public class Person
{
//下面定义两个成员变量
public String name;
public int age;
//下面定义了一个say方法
public void say(String content)
{
System.out.println(content);
}
}
public class PersonTest
{
public static void main(String[] args)
{
//使用Person类定义一个Person类型的变量
Person p;
//通过new关键字创建一个Person实例
//将Person实例赋给P变量
p = new Person();
//访问p变量的name实例变量,直接为该变量赋值
p.name = "hh";
//调用p的say()方法,声明say()方法时定义了一个形参,所以调用该方法时必须为形参指定一个值
p.say("学习java");
System.out.println(p.name);
}
}
因此程序中定义的Person类型的变量实际上是一个引用,它被存放在栈内存里,指向实际的Person对象;而真正的Person对象则存放在堆(heap)内存中。
栈内存的引用变量并未真正的存储对象的成员变量,对象的成员变量数据实际存储在堆内存中,引用变量只是指向该对内存里的对象。
//将p变量的值赋给P2变量
Person p2 = p;
如图:把p变量的值赋给p2变量,也就是把p变量保存的地址值赋给p2变量,这样p2变量和p变量将指向同一个Person对象。
1.3 匿名对象
匿名对象是指就是没有名字的对象。实际上,根据前面的分析,对于一个对象实例化的操作来讲,对象真正有用的部分是在堆内存里面,而栈内存只是保存了一个对象的引用名称(严格来讲是对象在堆内存的地址),所以所谓的匿名对象就是指,只开辟了堆内存空间,而没有栈内存指向的对象。
public class NONumberObject
{
public void say()
{
System.out.println("今天很开心");
}
public static void main(String[] args)
{
new NONumberObject().say();
}
}
2. 封装
面向对象有三大特征:封装,继承,多态
2.1 封装的定义
封装:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
2.2 封装的好处
对一个类或对象实现良好的封装,可以实现以下目的:
- 隐藏类的实现细节。
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对Field的不合理访问。
- 便于修改,提高代码的可维护性。
2.3 如何封装
为了实现良好的封装,需要从两个方面考虑:
- 将对象的Field和实现细节隐藏起来,不允许外部直接访问。
- 把方法暴露出来,让方法来控制对这些Field进行安全的访问和操作。
因此,封装实际上有两个方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。这两个方面都需要通过使用Java提供的访问控制符来实现。
2.4 访问控制符
java提供了一系列的访问控制符来设置基于类
,变量
,方法
,构造方法
等不同等级的访问权限。
- private(当前类访问权限):如果类里的一个成员(包括Field、方法和构造器等)使用private访问控制符来修饰,则这个成员只能在当前类的内部被访问。很显然,这个访问控制符用于修饰Field最合适,使用它来修饰Field就可以把Field隐藏在该类的内部。
- default(包访问权限):如果类里的一个成员(包括Field、方法和构造器等)或者一个外部类不使用任何访问控制符修饰,我们就称它是包访问权限,default访问控制的成员或外部类可以被相同包下的其他类访问。
- protected(子类访问权限):如果一个成员(包括Field、方法和构造器等)使用protected访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。在通常情况下,如果使用protected来修饰一个方法,通常是希望其子类来重写这个方法。
- public(公共访问权限):这是一个最宽松的访问控制级别,如果一个成员(包括Field、方法和构造器等)或者一个外部类使用public访问控制符修饰,那么这个成员或外部类就可以被所有类访问,不管访问类和被访问类是否处于同一个包中,是否具有父子继承关系。
- 访问控制符
用于控制一个类的成员是否可以被其他类访问
,对于局部变量而言,作用域为她所在的方法,不可能被其他类访问,因此不能使用访问控制符来修饰。 - 外部类可以使用public和包访问控制权限,使用public修饰的外部类可以被所有类
(包括不是同一个包下的类,但是需要导包)
使用,如声明变量、创建实例;不使用任何访问控制符修饰的外部类只能被同一个包中的其他类使用。 - public和default在同一个包里面定义的两个一样的类,会报错的。public 和default可以在同一个包下面跨类访问,public还可以跨包访问,当跨包访问时,需要导包。
2.5 封装的表现之一:私有
public class Person
{
//使用private修饰成员变量,将这些成员变量隐藏起来
//成员变量一旦私有化,就只能在本类中被访问,外部类无法访问
private String name;
private int age;
//提供方法操作name变量
public void setName(String name)
{
//执行合理性校验,要求用户名必须在2-6位之间
if(name.length()>6||name.length()<2)
{
System.out.println("您设置的人名不符合要求");
//结束方法
return;
}
else
{
this.name = name;
}
}
public String getName()
{
//name隐藏了this,this.name
return name;
}
//提供方法操作age成员变量
public void setAge(int age)
{
//执行合理性校验,要求用户的年靓必须在0-100之间
if(age>100||age<0)
{
System.out.println("您设置的年靓不合法");
//结束方法
return;
}else
{
this.age = age;
}
}
public int getAge()
{
return this.age;
}
}
定义了上面的Person类之后,该类的name和age两个Field只有在Person类内才可以操作和访问,在Person类之外只能通过各自对应的setter和getter方法来操作和访问它们。
public class PersonTest
{
public static void main(String[] args)
{
Person p = new Person();
//因为age变量已经被私有化,所以不能被外界访问
//p.age = 1000;
p.setAge(1000);
System.out.println("未能设置age成员变量"+p.getAge());
p.setAge(30);
System.out.println("成功设置age成员变量"+p.getAge());
p.setName("hh");
System.out.println(p.getName());
}
}
PersonTest类的main方法不可再直接修改Person对象的name和age两个Field,只能通过各自对应的setter方法来操作这两个Field值。因为使用setter方法来操作name和age两个Field,就允许程序员在setter方法中增加自己的控制逻辑,从而保证Person对象的name和age两个Field不会出现与实际不符的情形。避免了外界直接访问,导致安全隐患的产生。
类里的大部分成员变量使用private修饰,暴露出来给其他类调用的方法用 public修饰。
3. 方法
3.1 方法详解
- 方法是类或者对象的行为特征的抽象,java里的方法不能独立存在,所有的方法必须定义在类里,方法在逻辑上要么属于类,要么属于对象。
- 方法必须定义在类里,如果这个方法使用了static修饰,则这个方法属于类,否则属于类的实例。
- 同一个类里的一个方法调用另一个方法时,如果被调方法是普通方法则使用this作为调用者,如果被调方法时静态方法,则默认使用类作为调用者。
- 使用static修饰的方法可以使用类作为调用者,也可以使用对象作为调用者。没有使用static修饰的方法只能使用对象作为调用者。
public class MethodDemo2 {
public static void main(String[] args)
{
int c = add(2,3);
System.out.println(c);
}
/*
修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,....)
{
执行语句;
//return 语句结束该功能,并将方法的结果返回给调用者
//必须有return语句
return 返回值;
}
注意:如果返回值类型为void,return可以省略不写,void表示没有返回值的情况。
return放哪,方法在哪结束
*/
//写一个方法进行加法运算
/*
* 明确1:功能的结果是什么:是一个和,和是整数,返回值类型是int
* 明确2:功能的未知内容是什么:加数和被加数,都是int类型
*/
public static int add(int a,int b)
{
return a+b;
}
}
3.2 方法的参数传递机制
- 如果声明方法时包含了形参声明,则调用方法时必须给这些形参指定数值,调用方法时实际传给形参的参数值也被称为实参。java里的参数传递方式只有一种:值传递。
- 形参变量隶属于方法体,他们是方法的局部变量,只当被调用时才被创建,才被临时性的分配内存,调用结束后立即释放分配的内存单元。
示例一:基本类型的参数传递
public class PrimitiveTransferTest
{
public static void main(String[] args)
{
int a=6;
int b = 9;
swap(a,b);
System.out.println("交换结束后,变量a的值是"+a+";变量b的值是"+b);
}
public static void swap(int a,int b)
{
int temp = a;
a = b;
b = temp;
System.out.println("swap方法里a的值是:"+a+",b的值是:"+b);
}
}
值传递的实质:系统为形参执行初始化,就是把实参变量的值赋给方法的形参变量,真正操作的并不是实际的实参变量
示例二:引用类型的参数传递
public class ReferenceTransferTest
{
public static void main(String[] args)
{
/*
* 类的作用:
* 1.定义变量
* 2.创建对象
* 3.调用类的类方法或者访问类的类变量
*/
//使用类定义变量与创建对象
DataWrap dw = new DataWrap();
//使用类访问类的类变量
dw.a = 6;
dw.b = 9;
swap(dw);
System.out.println("交换结束后,变量a的值是"+dw.a+";变量b的值是"+dw.b);
}
public static void swap(DataWrap dw)
{
int temp = dw.a;
dw.a = dw.b;
dw.b = temp;
System.out.println("swap方法里a的值是:"+dw.a+";变量b的值是"+dw.b);
}
}
示例三:基本类型的参数传递
public class Demo
{
public static void main(String[] args)
{
int x = 4;
show(x);
System.out.println("x="+x);
}
public static void show(int x)
{
x=5;
}
}
示例四:引用类型的参数传递
public class Demo2
{
/*
如何判断对象里的东西?
对象里面装的都是属性值,
每个对象的属性相同,但是属性值不同,
所以对象中存放的是属性,在类中的属性就是成员变量
*/
int x=6;
public static void main(String[] args)
{
Demo2 d = new Demo2();
d.x=8;
show(d);
System.out.println("x="+d.x);
}
public static void show(Demo2 d)
{
d.x=7;
}
}
3.3 形参个数可变的方法
Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。
public class ArgsDemo
{
public static void main(String[] args)
{
ArgsDemo ad = new ArgsDemo();
ad.sum(3);
ad.sum(1,2,3);
ad.sum(1,2,3,4,5,6,7,7,8);
}
//可变参数问题
public void sum(int... n)
{
int sum = 0;
for(int i:n)
{
sum =sum+i;
}
System.out.println(sum);
}
}
3.4 方法重载
如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。
方法重载的要求就是两同一不同:同一个类中方法名相同,参数列表不同。
public class MethodDemo3 {
public static void main(String[] args)
{
add(2,3);
add(2,3,4);
}
//两个整数求和的功能
public static int add(int a,int b)
{
return a+b;
}
//三个整数求和的功能
public static int add(int a,int b,int c)
{
return a+b+c;
}
//两个小数的和
public static double add(double a,double b)
{
return a+b;
}
}
4. 构造函数
4.1 构造函数简介
- 如果程序员没有自定义构造函数,系统会为类提供一个默认的空参数构造函数
- 一旦程序员提供了自定义的构造函数,系统将不会提供默认的空参数构造函数
- 构造函数中有return语句,但是不能显示的写出来,return语句返回的是对象
- 构造函数可以私有化,一旦私有化就只能在本类中访问,其他类不能再创建本类的对象 。
- 构造函数私有化后不能被一般方法调用(因为没有对象),但是可以被其他构造方法调用
- 同一个类里具有多个构造器,多个构造器的形参列表不同,即被称为构造器重载。
4.2 构造函数示例
public class ConstructorTest
{
public String name;
public int count;
public ConstructorTest(String name,int count)
{
//this代表它初始化的对象
//将传入的参数赋给this代表的对象
this.name = name;
this.count = count;
}
public static void main(String[] args)
{
//下面这句话有错,因为一旦提供了自定义的构造器,默认的构造器就不存在了
//ConstructorTest ct = new ConstructorTest();
ConstructorTest ct = new ConstructorTest("kk",9000);
System.out.println(ct.name);
System.out.println(ct.count);
}
}
构造方法重载:
public class ConstructorOverload
{
public String name;
public int count;
public ConstructorOverload()
{
}
public ConstructorOverload(String name,int count)
{
this.name = name;
this.count = count;
}
public static void main(String[] args)
{
ConstructorOverload co = new ConstructorOverload();
ConstructorOverload co1 = new ConstructorOverload("kk",23);
System.out.println(co.name+"-----"+co.count);
System.out.println(co1.name+"-----"+co1.count);
}
}
构造函数私有化:
class Person1
{
private String name;
private int age;
//构造函数私有化
private Person1(String n)
{
name = n;
}
//使用其他构造函数调用私有化构造函数
Person1(String n,int a)
{
//用this带上参数列表的方式就可以访问本类中的其他构造函数
this(n);
// name = n;
age = a;
}
public void setName(String n)
{
name = n;
}
public String getName()
{
return name;
}
public void setAge(int a)
{
age = a;
}
public int getAge()
{
return age;
}
public void show()
{
System.out.println("name:"+name+"---"+"age:"+age);
}
}
public class PersonDemo2
{
public static void main(String[] args)
{
Person1 p = new Person1("小明",21);
p.setAge(14);
p.setName("ll");
p.show();
}
}
4.3 构造函数内存解析
class Person
{
String name;
int age;
//下面是构造函数的重载
Person(){}
Person(String n)
{
name = n;
}
Person(int a)
{
age = a;
}
Person(String n,int a)
{
name = n;
age = a;
}
//定义一个行为,这个行为是输出年龄和姓名
public void show()
{
System.out.println("name:"+name+"---"+"age:"+age);
}
}
public class PersonDemo
{
public static void main(String[] args)
{
//使用构造函数对对象进行初始化
Person p = new Person("xiaoming",30);
p.show ();
}
}
5. this关键字
5.1 this关键字的简介
- this关键字总是指向调用该方法的对象。根据this出现位置的不同, this作为对象的默认引用有两种情形:
- 构造器中引用该构造器正在初始化的对象。
- 在方法中引用调用该方法的对象。
- this带上参数列表的方式就可以访问本类中的其他构造函数。
- this关键字用于区分方法中成员变量和局部变量同名的情况。
- 用于构造函数的this语句必须定义在构造函数的第一行,因为初始化动作要先执行。
5.2 this关键字的用处代码示例
5.2.1 this关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或Field。
/*
* 注意:
* class Dog类前面不能再加public
* 在通常情况下,Java程序源文件的主文件名可以是任意的。
* 但有一种情况例外:如果Java程序源代码里定义了一个public类,
* 则该源文件的主文件名必须与该public类的类名相同。
* 由于Java程序源文件的文件名必须与public类的类名相同,
* 因此,一个Java源文件里最多只能定义一个public类。
*
*/
class Dog
{
//想要调用jump()方法,必须使用对象
public void jump()
{
System.out.println("正在执行jump方法");
}
//谁在调用这个方法,this就代表谁
public void run()
{
//思考:调用jump()方法,是否需要在run()方法中重新定义一个对象?
//不需要,因为程序在调用run()方法时,一定会定义一个Dog对象,那么就可以直接使用这个对象
Dog d = new Dog();
d.jump();
System.out.println("正在执行run方法");
}
}
public class DogTest
{
public static void main(String[] args)
{
Dog dog = new Dog();
dog.run();
}
}
大部分时候,一个方法访问该类中定义的其他方法、Field时加不加this前缀的效果是完全一样的。
将上面的程序修改为:
class Dog1
{
//想要调用jump()方法,必须使用对象
public void jump()
{
System.out.println("正在执行jump方法");
}
//谁在调用这个方法,this就代表谁
//每一个方法中都持有一个this
public void run()
{
//使用this引用调用run()方法中的对象this.jump()
//this.jump();
//java允许对象的一个成员直接调用另一个成员,可以省略this前缀
jump();
System.out.println("正在执行run方法");
}
}
public class DogTest2
{
public static void main(String[] args)
{
Dog1 dog = new Dog1();
dog.run();
}
}
5.2.2 使用this区分成员变量和局部变量同名的情况`
class Person2
{
private String name;
private int age;
/*
* 只要被对象调用的方法都持有this引用,(凡是访问了对象中的数据的方法都有this引用)
* 当成员变量和局部变量同名时,可以用this区分。
*/
Person2(String name,int age)
{
this.name = name;
this.age = age;
}
Person2(String n)
{
//注意这里面有this,只是没有写,实际上是this.name=n
//只不过重名时必须得写出来,没重名时可以不写
name = n;
}
public void setName(String name)
{
//使用this区分同名变量
this.name = name;
}
public String getName()
{
//return this.name
return name;
}
public void setAge(int a)
{
age = a;
}
public int getAge()
{
return age;
}
public void show()
{
//这里面也有this,只不过省略了
//java允许对象的一个成员直接调用另一个成员,可以省略this前缀
//System.out.println("name:"+this.name+"---"+"age:"+this.age);
System.out.println("name:"+name+"---"+"age:"+age);
}
public void method()
{
//p对象调用method,method所属于P,this再调用show()
//this.show()
show();
}
//不同类之间的方法的调用用对象
}
public class ThisDemo
{
public static void main(String[] args)
{
Person2 p = new Person2("小明",21);
p.method();
}
}
5.2.3 使用this调用构造方法`
使用this调用另一个重载的构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句。使用this调用重载的构造器时,系统会根据this后括号里的实参来调用形参列表与之对应的构造器。
public class Apple
{
public String name;
public String color;
public double weight;
public Apple() {};
public Apple(String name,String color)
{
this.name = name;
this.color = color;
}
public Apple(String name,String color,double weight)
{
this(name,color);
this.weight = weight;
}
}
5.2.4 利用this判断两个对象是否相等`
class Person4
{
private String name;
private int age;
Person4(String name,int age)
{
this.name = name;
this.age = age;
}
Person4(String n)
{
name = n;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setAge(int a)
{
age = a;
}
public int getAge()
{
return age;
}
public void show()
{
System.out.println("name:"+name+"---"+"age:"+age);
}
public void method()
{
show();
}
//判断两个对象是否为同龄人
public boolean equalsAge(Person4 pp)
{
/*
*1.形参pp传入的是一个Person对象,这个对象由实参传入
*2.this所指的对象是调用这个方法的对象
*/
//将当前对象的年龄和传入的对象的年龄进行比较
return pp.age == this.age;
}
}
public class ThisDemo2
{
public static void main(String[] args)
{
Person4 p = new Person4("小明",21);
Person4 p1 = new Person4("小强",23);
boolean b = p.equalsAge(p1);
System.out.println(b);
}
}
5.3 this关键字内存解析
示例1:
示例2:
示例3:
示例4:
6. static关键字
6.1 static的简介
- static用于修饰方法,成员变量等成员,static修饰的成员表明它属于这个类本身而不属于该类的单个实例。
- 用static修饰的静态成员不能访问非静态成员
- static真正的作用就是用于区分成员变量,方法,内部类,初始化代码块这四种成员到底属于类本身还是实例。
- 静态优先于对象存在
- 使用static修饰的方法和变量既可以使用类作为调用者,也可以使用对象作为调用者。
- 静态方法中不能有this
6.2 代码示例
6.2.1 静态方法无法访问非静态方法:
对于static修饰的方法使用类直接调用该方法,如果在static修饰的方法中使用this关键字,那么这个关键字无法执行合适的对象。所以static修饰的方法中不能使用this引用。
public class StaticAccesNonStatic
{
public void info()
{
System.out.println("简单地info方法");
}
public static void main(String[] args)
{
//因为main()方法是静态方法,而info()方法时非静态方法
//调用main()方法的是该类本身,而不是该类的实例
//因此省略的this无法指向有效的对象
//静态方法无法访问非静态方法
info();
}
}
如果确实需要在静态方法中访问另一个普通方法,则需要重新创建该类的一个对象
new StaticAccesNonStatic().info();
6.2.2 静态方法无法访问非静态成员
静态方法可以直接访问静态成员,但是不可以访问非静态成员
class Person{
private String name;
private int age;
static String country = "中国";
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void show(){
System.out.println("name:"+name+"----"+"age"+age);
}
public static void sleep()
{
//静态方法可以直接访问静态成员变量
System.out.println("呼呼"+country);
//静态方法不可以访问非静态的成员变量,因为静态方法时类调用的没有this
//System.out.println(name);
}
}
public class StaticDemo{
public static void main(String[] args){
Person p = new Person("hh",24);
Person.sleep();
}
}
6.2.3 何时使用静态static修饰
- 该函数没有访问过对象中的成员变量时,就需要使用static
- static 修饰成员变量,就会把共享数据和特有数据分开
class Person{
private String name;
private int age;
//因为中国是所有对象所共有的,所以加静态
static String country = "中国";
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void show(){
System.out.println("name:"+name+"----"+"age"+age);
}
//因为sleep()方法没有访问到对象中的成员变量所以可以使用静态
public static void sleep(){
System.out.println("呼呼"+country);
}
}
public class StaticDemo{
public static void main(String[] args){
Person p = new Person("hh",24);
//sleep方法并没有访问对象中的数据,因此不需要创建对象来调用它
Person.sleep();
}
}
6.3 static关键字内存解析
示例1:
示例2: