目录
3.3 static 关键字(重点理解)——看见static,本能反应,和对象无关
a. 直接通过类名称访问 static 修饰的属性,无需通过对象访问。
1. 类与对象的初步认知
面向过程:注重过程,在整个过程中所涉及的行为,就是功能。
面向对象:注重对象,也就是参与过程所涉及到的主体。是通过逻辑将一个个功能实现连接起来。
2. 类和类的实例化
类:一组具有相同特征的对象的抽象,对象就是这一类具体化的一个实例,类中定义了所有该类对象所共同具备的属性和方法。
类相当于一个模板,对象是由模板产生的样本。一个类,可以产生无数的对象。
所有类的对象都是引用数据类型,对于引用类型来说,默认值为 null 。Java 使用关键字 class 来声明类。
基本语法
// 创建类 class <class_name>{ field;//成员属性 method();//成员方法 } // 实例化对象 <class_name> <对象名> = new <class_name>();
class 为定义类的关键字,ClassName 为类的名字,{} 中为类的主体。
类中的元素称为:成员属性。
类中的函数称为:成员方法。
类的实例化
//代码示例 class Person { public int age; //成员属性 实例变量 public String name; public String sex; public void eat() { //成员方法 System.out.println("吃饭!"); } public void sleep() { System.out.println("睡觉!"); } } public class Main{ public static void main(String[] args) { Person person = new Person(); //通过new实例化对象 person.eat(); //成员方法调用需要通过对象的引用调用 person.sleep(); //产生对象 实例化对象 Person person2 = new Person(); Person person3 = new Person(); } } //输出结果 吃饭! 睡觉!
注意事项
1. new 关键字用于创建一个对象的实例。
2. 使用 . 来访问对象中的属性和方法。
3. 同一个类可以创建多个实例。
3. 类的成员
类的成员可以包含以下:字段、方法、代码块、内部类和接口等。
3.1 字段 / 属性 / 成员变量
类中在方法外部定义的变量。这样的变量我们称为 "字段" 或 "属性" 或 "成员变量" (三种称呼均可)。用于描述一个类中包含哪些数据。
class Person { public String name; // 字段 public int age; // 字段 } class Test { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name); System.out.println(person.age); } } // 执行结果 null 0
注意事项
1. 使用 . 访问对象的字段。
2. "访问" 既包含读,也包含写。
3. 对于一个对象的字段如果没有显式设置初始值,那么会被设置一个默认的初值。默认值规则
1. 对于各种数字类型,默认值为 0。
2. 对于 boolean 类型,默认值为 false。
3. 对于引用类型(String,Array,以及自定制类),默认值为 null。
认识 null
null 在 Java 中为 "空引用",表示不引用任何对象。类似于 C 语言中的空指针。如果对 null 进行 . 操作就会引发异常。
如果报错 NullPointerException ->空指针异常,一定使用了一个值为 null 的引用去访问成员属性或者成员方法了。(null 访问 static 属性不报错)
class Person { public String name; //成员属性 public int age; //成员属性 } class Test { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name.length()); // 获取字符串长度 } } // 执行结果 Exception in thread "main" java.lang.NullPointerException
字段就地初始化
不使用默认值,而是显式设定初值的操作。
class Person { public String name = "张三"; //赋初值 public int age = 18; //赋初值 } class Test { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name); System.out.println(person.age); } } // 执行结果 张三 18
3.2 方法 (method)
方法:用于描述一个对象的行为。
class Person { public int age = 18; public String name = "张三"; public void show() { System.out.println("我叫" + name + ", 今年" + age + "岁"); } } class Test { public static void main(String[] args) { Person person = new Person(); person.show(); //调用方法 } } // 执行结果 我叫张三, 今年18岁
此处 show 成员方法是和 person 实例对象相关联的。如果创建了其他实例,那么 show 的行为就会发生变化。
Person person2 = new Person(); person2.name = "李四"; person2.age = 20; person2.show(); //其他实例调用方法 // 执行结果 我叫李四, 今年20岁
3.3 static 关键字(重点理解)——看见static,本能反应,和对象无关
3.3.1 staic修饰的属性(称为类属性)
Java静态属性和类相关,和具体的实例对象无关。
当一个实例变量被 static 关键字修饰,他就表示类的属性,该类的所有对象共享这个属性,所有对象的属性值都一样。
static 修饰的属性在 JVM 方法区中存储,所有该类对象共享此属性。
class Person{ String name; // 成员变量,实例变量必须通过该类的对象来访问 int age; // 人这个属性中,需要有单独个体,才能得出该个体的name、age static String country; // 很多对象在堆中共有的成员属性country void show() { // 成员方法,实例方法,也是必须通过对象来访问 System.out.println("name = " + name +",age = " + age + ",country = " + country); } } public class Main{ public static void main(String[] args) { Person per1 = new Person(); per1.name = "张三"; per1.age = 20; per1.show(); Person per2 = new Person(); per2.name = "李四"; per2.age = 18; per2.show(); } } //输出结果 name = 张三,age = 20,country = 中国 name = 李四,age = 18,country = 中国
a. 直接通过类名称访问 static 修饰的属性,无需通过对象访问。
class Person{ static String country = "中国"; //很多对象在堆中共有的成员属性country } } public class Main{ public static void main(String[] args) { Person per1 = new Person(); System.out.println(Person.country); //用 类 访问static成员属性 System.out.println(per1.country); //用 对象 访问static成员属性 } } //输出结果 中国 中国
问题1:Java中能否在一个方法内部,定义一个 static 变量?
答:不能,方法中定义的变量全部都是局部变量,局部变量在栈中存储,而 static 变量在方法区,属性不可能同时存在于栈与方法区中。静态 main 方法中同样不能定义static 变量。
问题2:定义Person per2 = null; 执行 per2.country 是否报错"空指针异常"?
public static void main(String[] args) { Person per2 = null; System.out.println(per2.country); } class Person { static String country = "中国"; } //输出结果 中国
答:可以,static 属性称为类属性,通过类名称直接访问。故没有对象也可以调用(包含类的null引用),而不会出现 NullPointerException ->空指针异常报错。
b. static 与 final 的区别
class Person { //成员常量,都在堆中存储,必须在定义时赋值 final int age = 18; //静态变量,在方法区中存储,所有Person的对象共享这个country属性 static String country = "中国"; }
age 这个属性为成员常量,在类定义的时候就已赋值为18了。Person的所有对象都有age属性,且值都是18。把他定义为 static final ,可以节省空间,并且所有Person对象共享这个属性,全局唯一。
故在类中定义常量,一般使用全局常量,用 static final 共同修饰。
c. 常量的命名规则
所有单词全部大写,多个单词使用下划线分隔。
如,static final String STUDENT_SCHOOL = "清华大学";
小结
- static 变量称为类属性,在方法区中存储,该类的所有对象共享此变量。
- 若在类中定义了常量(定义时赋值),一般我们使用 static 和 final 共同修饰变成全局常量。
- 要使用类属性,我们通常直接通过类名称 . 属性名称,不推荐使用对象来调用类属性,这样不规范。
3.3.2 staic修饰的方法(类方法、工具方法)
staic修饰的方法,也是通过类名称直接访问的方法,没有对象也能访问。
类方法,如主方法
工具方法,如Arrays.sort(int[])、Arrays.copyOf(int[])
都是Arrays对外提供的操作数组的方法,将其设计为 static 方法
问题1:为何主方法是 static 方法?
答:主方法是程序的入口。如果主方法是成员方法,则需要 new 出对象调用,但没有程序入口,就产生不了对象。
程序从主方法开始运行,主方法要能够调用其他方法,所以设计为 static ,直接调用而不用产生对象。
问题2:静态方法能否访问成员变量、成员方法?
答:不能,成员方法、成员变量必须通过对象来调用。 static 里面没有该类对象,所以无法调用。
问题3:成员方法能否访问静态变量、静态方法?
答:可以,有对象才能调用成员方法,static 没对象都能调用,更何况有对象,那就更能调用了。
问题4:外部类能否使用 static 关键字修饰?
答:不能,编译出错。类定义出来就是要产生对象的,static 类没对象,就能调用。
小结
1. 在静态方法中只能调用静态方法或者静态属性,static家族之间可以相互调用。从中不能直接调用成员方法和成员属性,必须通过对象来调用。
2. 在成员方法中既可以调用成员方法,也可调用静态方法(此时都已经产生了该类对象,一定是可以访问静态域的)。
4. 封装
性质:保护性和易用性
4.1 private(实现封装的方法之一)
在Java中,所谓的权限修饰符,指的是修饰的属性、方法、类,到底可见的范围有多大。其中一共有四大访问修饰符,可见的范围又小到大依次为:
private < default(缺省关键字)-包访问权限 < protected < public
private 私有的,被private修饰的属性和方法,只在当前类的内部可见,出了类就对外部就完全隐藏了,外部不知道有其存在。
public 公共的,公开的被public修饰的属性和方法,在当前程序(项目)中都是可见的,都是可以使用的。
4.2 getter 和 setter 方法
类比现实
对于银行卡这个类来说,银行卡有卡号,余额,密码这三个属性。如果这三个属性直接暴露在外部,显然不安全,不能让这些属性通过对象直接就访问了。
想在类的外部去使用这些私有属性,需要使用类提供的 getter (取值)和 setter (修改值)。
// 公共访问权限,主类,这个类在当前项目中都是可见的 public class PrivateTest { public static void main(String[] args) { Bank bank = new Bank(); // 当password属性被private之后,对于这个属性就是一个保护 // 类的外部要想使用这个属性,必须按照我的规则去使用(getter和setter) bank.setPassword(); System.out.print("修改后的密码为:"); System.out.println(bank.getPassword()); } } // 银行类,缺省修饰符,包访问权限 class Bank { private int cardNum; // 卡号 private double sal; // 余额 private String password = "123456"; // 密码 // alt + insert 快速生成getter和setter方法 // get+属性名称 = 方法命名 public int getCardNum() { //取值 return cardNum; } public double getSal() { //取值 return sal; } public String getPassword() { //取值 return password; } public void setPassword() { Scanner scanner = new Scanner(System.in); int count = 0; // 验证当前密码是否正确才能修改 while (true) { System.out.println("请输入您现在的旧密码:"); String oldPass = scanner.nextLine(); count ++; // 所有引用类型的对象比较使用equals方法 if (oldPass.equals(password)) { // 密码输入正确,修改密码 System.out.println("密码正确,正在改密码"); System.out.println("请输入您的新密码:"); String newPass = scanner.nextLine(); password = newPass; System.out.println("密码修改成功!"); break; } else { // 输入密码有误 System.out.println("密码错误,请查证后再试"); if (count == 3) { System.out.println("尝试次数过多,银行卡已经锁定"); break; } } } } }
问题1:private 能否修饰外部类?
答:不能,类定义出来就是为了产生对象,让外部使用。用 private 封装,在外面就看不到了,从而不能使用对象。
5. 构造方法
构造方法是类中非常特殊的一类方法,使用关键字 new 实例化对象时,实际上就调用的是该类的构造方法。构造方法的作用就是产生对象。
使用关键字 new 产生一个对象时,大致分为以下两步:
1.为对象在堆中分配空间
2.调用对象的构造方法为对象成员变量赋值
5.1 基本语法
构造方法语法规则:
1. 方法名称与类名称完全相同。
2. 构造方法没有返回值声明(无void)。
3. 一个类中至少存在一个构造方法,若没有显示定义,编译器会生成—个默认的无参构造。
定义构造方法
public class TestClass { public static void main(String[] args) { Person per = new Person(); } } class Person { private String name; private int age; public Person() { //构造方法的定义 System.out.println("Person的无参构造"); } } //输出结果 Person的无参构造
若没有定义构造方法,编译器会自动生成一个。
当类中自定义了构造方法,默认的无参构造就不再生成。
构造方法在内存空间赋值
构造方法的重载
构造方法是为了类中的成员变量赋值的,此时的重载只可能是参数的个数不同。(成员变量的类型在类定义时就指定好了,只是初始化的变量个数不同。)
public class TestClass { public static void main(String[] args) { Person per1 = new Person(); Person per2 = new Person("小明"); Person per3 = new Person("小明", 18); // 这三个对象构造方法都已经调用结束,初始化完成 } } class Person { private String name; private int age; private String sex; public Person() { //无参构造方法的定义 //mame = null;age = 0;sex = null; //首行赋默认值 System.out.println("Person的无参构造"); } public Person(String n) { //传一个参数 //mame = null;age = 0;sex = null; //首行赋默认值 name = n; System.out.println("name = " + name); System.out.println("Person的一个参数的有参构造"); } public Person(String n, int a) { //传两个参数 //mame = null;age = 0;sex = null; //首行赋默认值 name = n; age = a; System.out.println("name = " + name + ",age = " + age); System.out.println("Person的两个参数的有参构造"); } } //输出结果 Person的无参构造 name = 小明 Person的一个参数的有参构造 name = 小明,age = 18 Person的两个参数的有参构造
成员变量定义时赋初值
成员变量只有在产生对象时,才会在堆中分配空间,然后调用构造方法赋值。
问题:此处对象 per1 能否调用构造方法 Person ?
答:不能,报错 java:找不到符号。
符号:方法 Person()
位置:类型为Person的变量 per1自己调用自己的构造方法,JVM 产生对象时调用构造方法,对象实例化结束,无法在程序中手动调用构造方法再次实例化对象。
5.2 this 关键字
this 关键字表示当前对象的引用
5.2.1 this 调用当前对象的成员变量
public class ThisTest { public static void main(String[] args) { Student student = new Student("小明", 20, "男"); student.show(); } } class Student { private String name; private int age; private String sex; public Student(String name, int age, String sex) { name = name; //就近匹配 age = age; sex = sex; System.out.println("Student类的有参构造"); } public void show() { System.out.println("name = " + name + ",age = " + age + ",sex = " + sex); } } //输出结果 Student类的有参构造 name = null,age = 0,sex = null
程序的设计理念,就近匹配原则,编译器会找到最近的相同名称的变量在哪。上述情况,形参自己等于了自己,对类中的成员变量没有任何影响。
如何打破就近匹配原则,从类中找同名变量?——>使用 this 关键字。形参的值给了成员变量。
//使用 this 关键字,改正上述问题 public Student(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; System.out.println("Student类的有参构造"); } //输出结果 Student类的有参构造 name = 小明,age = 20,sex = 男
5.2.2 this 调用类中的方法
1. this 调用普通成员方法
public class ThisTest { public static void main(String[] args) { Student student = new Student(); student.fun(); } } class Student { private String name; private int age; private String sex; public void test() { System.out.println("Student类的test成员方法"); } public void fun() { //此处没有用对象调用 test(); test(); System.out.println("Student类的fun成员方法"); } } //输出结果 Student类的test成员方法 Student类的fun成员方法
此处 test() 未使用对象却依旧可以调用,原因是编译器编译后默认加上 this ,使其变成当前对象的引用 this.test() 。
2. this 表示构造方法间的相互调用
若不同参数的构造方法之间出现了重复的调用,可以使用 this(参数) 调用其他的构造方法。
public Student() { System.out.println("********************"); } public Student(String name) { //调用无参构造 this(); this.name = name; } public Student(String name, int age) { this(name); this.age = age; }
3. 表示当前对象的引用
public class ThisTest { public static void main(String[] args) { Student stu1 = new Student(); System.out.println(stu1); stu1.whoAmI(); Student stu2 = new Student("张三"); System.out.println(stu2); stu2.whoAmI(); Student stu3 = new Student("张三",18); } } class Student { private String name; private int age; private String sex; public void whoAmI() { System.out.println(this); } public Student() { System.out.println("********************"); } public Student(String name) { this(); this.name = name; } public Student(String name, int age) { this(name); this.age = age; } }
6. 代码块
代码块:指的就是使用(括起来的一段代码称为代码块。
根据定义的代码块的位置以及关键字的不同分为以下四种代码块。
6.1 普通代码块
定义在方法中,使用 {} 括起的代码块。
public class CodeTest { public static void main(String[] args) { { //普通代码块 int a = 10; } int a = 20; System.out.println(a); } } //输出结果 20
6.2 成员代码块
直接定义在类中,不加任何修饰符,使用 {} 括起的代码块,也叫构造块。构造代码块优先于构造方法执行,有几个对象就调用几次构造块。
public class CodeTest { public static void main(String[] args) { Animal animal1 = new Animal(); Animal animal2 = new Animal(); } } class Animal { private String name; { //构造快 System.out.println("2.Animal构造块"); } public Animal() { System.out.println("1.Animal的无参构造"); } } //输出结果 2.Animal构造块 1.Animal的无参构造 2.Animal构造块 1.Animal的无参构造
执行顺序,先执行构造块,然后执行有参构造。
public class CodeTest { public static void main(String[] args) { Animal animal1 = new Animal("李四"); System.out.println(animal1.name); } } class Animal { String name; { //构造快 name = "test"; System.out.println("2.Animal构造块"); } public Animal(String name) { this.name = name; } } //输出结果 2.Animal构造块 李四
6.3 静态代码块
定义在类中,使用 static 修饰的代码块,当且仅当类加载到内存时执行一次,且只执行一次。
public class CodeTest { public static void main(String[] args) { Animal animal1 = new Animal("李四"); Animal animal2 = new Animal(); } } class Animal { String name; { //构造快 name = "test"; System.out.println("2.Animal构造块"); } public Animal() { System.out.println("1.Animal的无参构造"); } public Animal(String name) { this.name = name; System.out.println("1.Animal的有参构造"); } static { //静态块 System.out.println("3.Animal的静态代码块"); } } //输出结果 3.Animal的静态代码块 2.Animal构造块 1.Animal的有参构造 2.Animal构造块 1.Animal的无参构造
当类中存在静态变量、静态代码块有 1 至 N 个。则在类加载时,这些静态变量的赋值以及静态代码块,最终都会合并为一个大的静态代码块由JVM执行。
主类静态代码块执行顺序
主类静态代码块优先于主方法执行。JVM要执行主方法,首先要加载主类,主类加载后,静态库块就执行了。
public class CodeTest { static { System.out.println("2.主类的静态代码块"); } public static void main(String[] args) { System.out.println("1.进入主方法"); } } //输出结果 2.主类的静态代码块 1.进入主方法
问题 :若输出 a ,其值为多少?
class Animal { static int a = 10; static { //静态代块 int a = 100; } } //输出结果 100
答:static int a 存在于方法区中。类定义的时候 a 就会有初始值(10),然后这个类就被放入方法区中,此时这个类只是被定义了但还没有被加载。
当主方法使用了 Animal 类时,才将 Animal 从方法区加载到内存(称为类加载),类加载后,static 代码块就被执行(a = 10 ——> 100)。
7. 匿名对象
new 出来的对象没有引用指向,只会使用一次,使用后即销毁。new Person();
常用于测试类中的某些功能,使用一次后被JVM销毁。
public class CodeTest { public static void main(String[] args) { //Animal的匿名对象 System.out.println(new Animal()); } } //输出结果 Animal@1b6d3586
8. toString方法
当一个引用类型的变量调用 println 函数打印时,默认输出的都是引用类型的地址。(不是真正的内存地址,Java中程序员是无法知道任何确切的内存地址)
public class toString { public static void main(String[] args) { System.out.println(new Animal1("小明")); } } class Animal1 { private String name; public String toString() { String ret = "name = " + this.name; return ret; } public Animal1(String name) { this.name = name; } } //输出结果 name = 小明