1.类与对象的定义与使用
① 类与对象的概念
所谓类,是一个共性的概念,而对象指的是一个具体的、可以使用的事物。
首先产生类(类是生产对象的蓝图),而后才可以产生对象,对象的所有行为一定在类中进行了完整的定义。
类的组成:
1.属性(变量,描述每个对象的具体特点)
2.方法(操作的行为)
② 类与对象的定义与使用
-
定义一个类
语法如下:
class 类名称{
属性1;
属性2;
...
属性n;
方法1(){};
方法2(){};
...
方法n(){};
}
注意:此时的方法不再由主类直接调用,而是由该类的实例化对象进行调用
类对象实例化语法:
类名称 类对象名称 = new 类名称();
示例:定义一个Person类并实例化类对象
class Person{
public String name;
public int age;
public Person(String n,int a){
name = n;
age = a;
}
public void getPersonInfo(){
System.out.println("姓名:" + name + ",年龄" + age);
}
}
public class Test{
Person p1 = new Person("李四",19);//直接通过构造函数实例化
Person p2 = new Person();
//通过对象修改类属性、调用类方法
p2.name = "张三";
p2.age = 18;
p1.getPersonInfo();
p2.getPersonInfo();
}
注意:只要出现了关键字new,就开辟了内存空间;
Java中,所谓的性能调优,调整的就是内存问题。
③ 对象内存分析
栈内存(虚拟机局部变量表):存放局部变量(包含编译期可知的各种基本数据类型,对象引用-即堆内存地址,可以简单理解为对象的名称)。
Java栈是与线程对应起来的,每当创建一个线程,JVM就会为这个线程创建一个对应的Java栈。
堆内存:保存真正的数据。即对象的属性信息。
代码示例:
class Person{
String name;
int age;
}
public class Test{
public static void main(String[] args){
Person per = new Person();
per.name = "张三";
per.age = 30;
}
}
对于主函数中的第一条语句:Person per = new Person();//在堆上分配内存并产生Person类的对象per引用这部分内存。
内存分析如下:
接下来两句:
per.name = "张三";
per.age = 30;
内存图分析如下:
注意:对象必须在实例化之后调用,否则会产生NullPointerExecption(运行时错误),编译时不会出错;
只有引用类型(数组、类、接口)才会产生此类异常,因此在后续写代码过程中如果出现此类异常,就可以根据出错位置查看引用数据类型是否初始化。
④ 引用传递分析
本质:一块堆内存可以被多个栈内存所指向
代码示例:
Person p1 = new Person();
Person p2 = new Person();
p2 = p1;
内存分析图如下:
垃圾空间:没有任何栈内存指向的堆内存空间
所有的垃圾空间会不定期GC,GC不会影响性能,所以在开发中一定要控制好对象的产生数量(无用的对象尽可能少产生)
2.private实现封装处理&构造方法(匿名对象)
① private实现封装处理
以上所写的所有程序都属于无封装程序。
在不进行封装的情况下,在主类中可以任意对Person类的对象属性进行修改。此时,要回避此类问题,让类内部操作堆外部不可见(对象不能直接操作属性),可以使用private进行封装。
示例:在Person类中使用private封装属性
private String name;
private int age;
此时使用private堆属性进行了封装,要访问私有属性,按照Java的设计原则必须在类中提供以下两种方法:
1.getter方法:主要用于进行属性内容的设置和修改
2.getter方法:主要用于属性内容的取得
示例:扩展Person类中的方法,实现对Person类属性的封装
class Person{
private String name;
private int age;
public void setName(String a){
name = n;
}
public String getName(){
return name;
}
public void setAge(int i){
if(i > 0 && i <= 100){
age = i;
}else{
age = 0;
}
}
public int getAge(){
return age;
}
public void getPersonInfo(){
return "姓名:"+name+",年龄:"+age;
}
}
public class Test{
public static void main(String[] args){
Person p = new Person();
p.setName("张三");
p.setAge(18);
p.getPersonInfo();
}
}
通过以上代码我么可以发现,private实现封装的最大特征在于:只允许本类访问,而不允许外部类访问。
- 类的设计原则:
1)在编写类时,类中的所有属性必须使用private封装
2)属性若要被外部访问,必须定义setter、getter方法。
②构造方法与匿名对象
回顾产生对象的方法:
①类名称 ②对象名称 = ③new ④类名称();
-
① 任何对象都有对应的类,类时对象的蓝图 ;
-
② 对象名称是一个唯一的标记,引用一块堆内存;
-
③ new表示开辟新的堆内存空间;
-
④ 构造方法
通过上述分析可知,所谓的构造方法就是使用关键字new实例化新对象来进行调用的操作方法。对于构造方法的定义,也需要遵循以下原则:
1.方法名必须与类名称相同;
2.构造方法没有返回值类型声明;
3.每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)
问题:构造方法无返回值,为什么没有void声明?
首先我们先来看一下类的组成:属性、构造方法、普通方法
1.属性是在对象开辟堆内存时开辟的空间;
2.构造方法是在使用new后调用的;
3.普通方法是在空间开辟了,构造方法执行之后可以多次调用的。
部分代码示例:
public void Person(){} //命名不标准的普通方法
public Person(){} //无参构造方法
从以上描述我们可以看出,编译器是根据程序结构来区分普通方法和构造方法的,所以在构造方法前没有返回值类型声明。
若类中定义了构造方法,则默认的无参构造将不再生成。
示例:使用构造方法设置对象属性
class Person{
private String name;
private int age;
public Person(String n,int a){
name = n;
age = a;
}
public void setName(String n){
name = n ;
}
public String getName(){
return name;
}
public void setAge(int i){
if (i>0&&i<=200) {
age = i ;
}else {
age = 0 ;
}
}
public int getAge(){
return age;
}
public void getPersonInfo(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
public class Test{
public static void main(String[] args) {
Person person = new Person("张三",-200);//使用构造函数对对象属性进行赋值
person.getPersonInfo();
}
}
构造方法的调用和对象内存分配几乎是同步完成的,因此我们可以利用构造方法来为类中的属性进行初始化操作(可以避免多次的setter调用)
构造方法重载:参数类型或个数不同
示例:
public Person(){
System.out.println("---无参构造---");
}
public Person(String n){
name = n;
System.out.println("---有参构造---");
}
建议:若干构造方法,最好按照参数个数升序或降序编写;
在进行类定义时:①定义属性-->②定义构造方法-->③定义普通方法
范例:匿名对象
new Person("张三",20).getPersonInfo();
注意:由于匿名对象不会有任何的栈空间指向,所以使用一次后就成为垃圾空间。
3.this关键字
① 用途
ⅰthis调用本类属性
ⅱ this调用本类方法
ⅲ this表示当前对象
② this调用本类属性
代码示例:不用this关键字时
class Person{
private String name;
private int age;
public Person(String name,int age){
name = name ;
age = age ;
}
public String getPersonInfo(){
return "姓名:" + name + ",年龄:"+age;
}
}
public class Test{
public static void main(String[] args) {
Person per = new Person("张三",20);
System.out.println(per.getPersonInfo());
}
}
通过上述代码我们可以发现:当参数与类中属性同名时,类中属性无法被正确赋值。此时若加上this关键字就可以解决这个问题。
示例:使用this关键字修改上述代码
public Person(String name,int age){
this.name = name;
this.age = age;
}
为了避免类似问题,只要在类中方法访问类中属性,一定要加this关键字。
②this调用本类方法
this调用本类方法有两种情况:
ⅰ调用普通方法:this.方法名称(参数);
ⅱ 调用构造方法:this(参数);
示例:this调用普通方法
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this,age = age;
this.print(); //this调用普通方法
}
public String getPersonInfo(){
return "姓名:" + name + ",年龄:"+age;
}
public void print(){
System.out.println("*****************");
}
}
public class Test{
public static void main(String[] args) {
Person per = new Person("张三",20);
System.out.println(per.getPersonInfo());
}
}
示例:多个构造函数同时存在
class Person{
private String name;
private int age;
public Person(){
System.out.println("********产生一个新的Person对象********");
}
public Person(String name){
System.out.println("********产生一个新的Person对象********");
this.name = name ;
}
public Person(String name,int age){
System.out.println("********产生一个新的Person对象********");
this.name = name ;
this.age = age ;
}
public String getPersonInfo(){
return "姓名:" + name + ",年龄:"+age;
}
}
public class Test{
public static void main(String[] args) {
Person per1 = new Person();
Person per2 = new Person("张三");
Person per3 = new Person("李四",20);
System.out.println(per1.getPersonInfo());
System.out.println(per2.getPersonInfo());
System.out.println(per3.getPersonInfo());
}
}
Java中支持构造函数的相互调用
示例:this调用构造函数(上述代码的修改)
public Person(){
System.out.println("---产生一个新的Person对象---");
}
public Person(String name){
this();//调用本类无参构造
this.name = name;
}
public Person(String name,int age){
this(name);//调用本类有参构造
this.age = age;
}
- 使用this调用构造方法时应注意:
1.this调用构造方法的语句必须放在构造方法首行;
2.使用this调用构造方法时,必须留有出口(即不能成环)
④ 使用this表示当前对象
示例:
class Person{
public void print(){
System.out.println("[PRINT]方法:"+this);
}
}
public class Test{
public static void main(String[] args) {
Person p1 = new Person();
System.out.println("[MAIN]方法:"+p1);
p1.print();
System.out.println("=================");
Person p2 = new Person();
System.out.println("[MAIN]方法:"+p2);
p2.print();
}
}
只要对象调用了本类中的方法,这个this就表示当前执行的对象。
5.static关键字
static可以修饰属性和方法
① static属性(类属性)
示例:实例属性的内存分析
class Person{
String Country = "中国";
String name;
int age;
public void getPersonInfo(){
System.out.println("姓名:"+this.name+",年龄:"+this.age+",国家:"+this.Country);
}
}
public class Test{
public static void main(String[] args){
Person p1 = new Person();
p1.name = "张三";
p1.age = 19;
Person p2 = newPerson();
p2.name = "李四";
p2.age = 20;
p1.getPersonInfo();
p2.getPersonInfo();
}
}
内存图解:
传统属性所具备的特征:保存在堆内存中,且每个对象独享属性。
描述共享属性,只需要再属性前添加static关键字即可。
static属性又称为类属性,保存在全局数据区的内存之中,所有对象都可以进行该数据区的访问。
若修改上述代码堆country的定义为:
static String Country = "中国";
此时内存分析图如下:
结论:
1.访问static属性(类属性)应使用类名称.属性名
2.所有非static属性(实例变量)必须在对象实例化后使用,而static属性(类属性)不受对象实例化控制。
此时,如果我们修改country属性,所有对象都同步此值
定义类时,如何选择实例变量和类属性变量?
1.定义类时,一般情况下不会考虑static属性,以非static属性为主
2.如果需要描述共享属性的概念,或者不受对象实例化的控制,使用static
② static方法(类方法)
使用static定义的方法,直接通过类名访问
示例:
class Person{
private static String country;
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public static void setCountry(String c){
country = c;
}
public void getPersonInfo(){
System.out.println("姓名:"+this.name+",年龄:"+this.age+",国家:"+this.country);
}
}
public class Test{
public static void main(){
Person.setCountry("中国");
Person person = new Person("张三",18);
person.getPersonInfo();
}
}
注意:
(1)所有static方法不允许调用非static定义的属性或方法;
(2)所有的非static方法允许访问static方法或属性。
使用static定义方法的目的:某些方法不希望受类的控制,即可以在没有实例化对象的时候执行(广泛存在于工具类中)