1.构造器
我们发现我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢。
可以,Java给我们提供了构造器(下·)。
1.1 构造器的作用
new 对象,并在 new 对象的时候为实例变量赋值
1.2 构造器的语法格式
构造器又称为构造方法,那是因为它长的很像方法。但是和方法还是有所区别的。
【修饰符】 class 类名{
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}
代码如下:
public class Student {
private String name;
private int age;
// 无参构造
public Student() {}
// 有参构造
public Student(String name,int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}
注意事项:
-
构造器名必须与它所在的类名必须相同。
-
它没有返回值,所以不需要返回值类型,甚至不需要void
-
如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
-
如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
-
构造器是可以重载的,既可以定义参数,也可以不定义参数。
-
构造器的修饰符只能是权限修饰符,不能被其他任何修饰
public class TestStudent {
public static void main(String[] args) {
//调用无参构造创建学生对象
Student s1 = new Student();
//调用有参构造创建学生对象
Student s2 = new Student("张三",23);
System.out.println(s1.getInfo());
System.out.println(s2.getInfo());
}
}
1.3 同一个类中的构造器互相调用
-
this():调用本类的无参构造
-
this(实参列表):调用本类的有参构造
-
this()和this(实参列表)只能出现在构造器首行
-
不能出现递归调用
public class Student {
private String name;
private int age;
// 无参构造
public Student() {
// this("",18);//调用本类有参构造
}
// 有参构造
public Student(String name,int age) {
this();//调用本类无参构造
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}
1.4 继承时构造器如何处理
-
子类继承父类时,不会继承父类的构造器。只能通过super()或super(实参列表)的方式调用父类的构造器。
-
super();:子类构造器中一定会调用父类的构造器,默认调用父类的无参构造,super();可以省略。
-
super(实参列表);:如果父类没有无参构造或者有无参构造但是子类就是想要调用父类的有参构造,则必须使用super(实参列表);的语句。
-
super()和super(实参列表)都只能出现在子类构造器的首行
public class Employee {
private String name;
private int age;
private double salary;
public Employee() {
System.out.println("父类Employee无参构造");
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
System.out.println("父类Employee有参构造");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age +",薪资:" + salary;
}
}
public class Manager extends Employee{
private double bonusRate;
public Manager() {
super();//可以省略
}
public Manager(String name, int age, double salary, double bonusRate) {
super(name, age, salary);//调用父类的有参构造
this.bonusRate = bonusRate;
}
public double getBonusRate() {
return bonusRate;
}
public void setBonusRate(double bonusRate) {
this.bonusRate = bonusRate;
}
@Override
public String getInfo() {
return super.getInfo() +",奖金比例:" + bonusRate;
}
}
public class TestEmployee {
public static void main(String[] args) {
Manager m1 = new Manager();
System.out.println(m1.getInfo());
Manager m2 = new Manager("张三",23,20000,0.1);
System.out.println(m2.getInfo());
}
}
形式一:
class A{
}
class B extends A{
}
class Test{
public static void main(String[] args){
B b = new B();
//A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
//但是因为都是默认的,没有打印语句,看不出来
}
}
形式二:
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
}
class Test{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类默认有一个无参构造,
//B类的默认无参构造中会默认调用A类的无参构造
//可以看到会输出“A类无参构造器"
}
}
形式三:
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
B(){
System.out.println("B类无参构造器");
}
}
class Test{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类显示声明一个无参构造,
//B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
//可以看到会输出“A类无参构造器"和"B类无参构造器")
}
}
形式四:
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
B(){
super();
System.out.println("B类无参构造器");
}
}
class Test{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类显示声明一个无参构造,
//B类的无参构造中明确写了super(),表示调用A类的无参构造
//可以看到会输出“A类无参构造器"和"B类无参构造器")
}
}
形式五:
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
System.out.println("B类无参构造器");
}
}
class Test05{
public static void main(String[] args){
B b = new B();
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个无参构造,
//B类的无参构造没有写super(...),表示默认调用A类的无参构造
//编译报错,因为A类没有无参构造
}
}
形式六:
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
super();
System.out.println("B类无参构造器");
}
}
class Test06{
public static void main(String[] args){
B b = new B();
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个无参构造,
//B类的无参构造明确写super(),表示调用A类的无参构造
//编译报错,因为A类没有无参构造
}
}
形式七:
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(int a){
super(a);
System.out.println("B类有参构造器");
}
}
class Test07{
public static void main(String[] args){
B b = new B(10);
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个有参构造,
//B类的有参构造明确写super(a),表示调用A类的有参构造
//会打印“A类有参构造器"和"B类有参构造器"
}
}
形式八:
class A{
A(){
System.out.println("A类无参构造器");
}
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
super();//可以省略,调用父类的无参构造
System.out.println("B类无参构造器");
}
B(int a){
super(a);//调用父类有参构造
System.out.println("B类有参构造器");
}
}
class Test8{
public static void main(String[] args){
B b1 = new B();
B b2 = new B(10);
}
}
1.5 IDEA生成构造器:Alt + Insert
1.6 IDEA查看构造器和方法形参列表快捷键:Ctrl + P
2.非静态代码块(了解)
2.1 非静态代码块的作用
和构造器一样,也是用于实例变量的初始化等操作。
2.2 非静态代码块的意义
如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。
2.3 非静态代码块的执行特点
所有非静态代码块中代码都是在new对象时自动执行,并且一定是先于构造器的代码执行。
2.4 非静态代码块的语法格式
【修饰符】 class 类{
{
非静态代码块
}
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}
2.5 非静态代码块的应用
案例:
(1)声明User类,
-
包含属性:username(String类型),password(String类型),registrationTime(long类型),私有化
-
包含get/set方法,其中registrationTime没有set方法
-
包含无参构造,
-
输出“新用户注册”,
-
registrationTime赋值为当前系统时间,
-
username就默认为当前系统时间值,
-
password默认为“123456”
-
-
包含有参构造(String username, String password),
-
输出“新用户注册”,
-
registrationTime赋值为当前系统时间,
-
username和password由参数赋值
-
-
包含public String getInfo()方法,返回:“用户名:xx,密码:xx,注册时间:xx”
(2)编写测试类,测试类main方法的代码如下:
public static void main(String[] args) {
User u1 = new User();
System.out.println(u1.getInfo());
User u2 = new User("lee","8888");
System.out.println(u2.getInfo());
}
如果不用非静态代码块,User类是这样的:
public class User {
private String username;
private String password;
private long registrationTime;
public User() {
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();
username = registrationTime+"";
password = "123456";
}
public User(String username,String password) {
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRegistrationTime() {
return registrationTime;
}
public String getInfo(){
return "用户名:" + username + ",密码:" + password + ",注册时间:" + registrationTime;
}
}
如果提取构造器公共代码到非静态代码块,User类是这样的:
public class User {
private String username;
private String password;
private long registrationTime;
{
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();
}
public User() {
username = registrationTime+"";
password = "123456";
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRegistrationTime() {
return registrationTime;
}
public String getInfo(){
return "用户名:" + username + ",密码:" + password + ",注册时间:" + registrationTime;
}
}
3.实例初始化过程(了解)
3.1 实例初始化的目的
实例初始化的过程其实就是在new对象的过程中为实例变量赋有效初始值的过程
3.2 实例初始化相关代码
在new对象的过程中给实例变量赋初始值可以通过以下3个部分的代码完成:
(1)实例变量直接初始化
(2)非静态代码块
(3)构造器
当然,如果没有编写上面3个部分的任何代码,那么实例变量也有默认值。
3.3 实例初始化方法
实际上我们编写的代码在编译时,会自动处理代码,整理出一个或多个的<init>(...)实例初始化方法。一个类有几个实例初始化方法,由这个类就有几个构造器决定。
实例初始化方法的方法体,由4部分构成:
(1)super()或super(实参列表)
-
这里选择哪个,看原来构造器首行是super()还是super(实参列表)
-
如果原来构造器首行是this()或this(实参列表),那么就取对应构造器首行的super()或super(实参列表)
-
如果原来构造器首行既没写this()或this(实参列表),也没写super()或super(实参列表) ,默认就是super()
(2)非静态实例变量的显示赋值语句
(3)非静态代码块
(4)对应构造器中剩下的的代码
特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面
3.4 实例初始化执行特点
-
创建对象时,才会执行
-
每new一个对象,都会完成该对象的实例初始化
-
调用哪个构造器,就是执行它对应的<init>实例初始化方法
-
子类super()还是super(实参列表)实例初始化方法中的super()或super(实参列表) 不仅仅代表父类的构造器代码了,而是代表父类构造器对应的实例初始化方法。
3.5 演示父类实例初始化
public class Father {
private int a = 1;
public Father(){
System.out.println("Father类的无参构造");
}
public Father(int a, int b){
System.out.println("Father类的有参构造");
this.a = a;
this.b = b;
}
{
System.out.println("Father类的非静态代码块1,a = " + a);
System.out.println("Father类的非静态代码块1,b = " + this.b);
}
private int b = 1;
{
System.out.println("Father类的非静态代码块2,a = " + a);
System.out.println("Father类的非静态代码块2,b = " + b);
}
public String getInfo(){
return "a = " + a + ",b = " + b;
}
}
public class TestFather {
public static void main(String[] args) {
Father f1 = new Father();
System.out.println(f1.getInfo());
System.out.println("-----------------------");
Father f2 = new Father(10,10);
System.out.println(f2.getInfo());
}
}
3.6 演示子类实例初始化
public class Son extends Father {
private int c = 1;
{
System.out.println("Son类的非静态代码块,c = " + c);
}
public Son() {
System.out.println("Son类的无参构造");
}
public Son(int a, int b, int c) {
super(a, b);
this.c = c;
System.out.println("Son类的有参构造");
}
@Override
public String getInfo() {
return super.getInfo() + ",c = " + c;
}
}
public class TestSon {
public static void main(String[] args) {
Son s1 = new Son();
System.out.println(s1.getInfo());
System.out.println("---------------");
Son s2 = new Son(10,10,10);
System.out.println(s2.getInfo());
}
}
4.关键字和API
1.this和super关键字
1.1 this和super的意义
this:当前对象
-
在构造器和非静态代码块中,表示正在new的对象
-
在实例方法中,表示调用当前方法的对象
super:引用父类声明的成员
无论是this和super都是和对象有关的。
1.2 this和super的使用格式
-
this
-
this.成员变量:表示当前对象的某个成员变量,而不是局部变量
-
this.成员方法:表示当前对象的某个成员方法,完全可以省略this.
-
this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
-
-
super
-
super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
-
super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
-
super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
-
1.3 避免子类和父类声明重名的成员变量
特别说明:应该避免子类声明和父类重名的成员变量
因为,子类会继承父类所有的成员变量,所以:
-
如果重名的成员变量表示相同的意义,就无需重复声明
-
如果重名的成员变量表示不同的意义,会引起歧义
在阿里的开发规范等文档中都做出明确说明
1.4 解决成员变量重名问题
-
如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
-
如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量
-
如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
class Father{
int a = 10;
int b = 11;
}
class Son extends Father{
int a = 20;
public void test(){
//子类与父类的属性同名,子类对象中就有两个a
System.out.println("子类的a:" + a);//20 先找局部变量找,没有再从本类成员变量找
System.out.println("子类的a:" + this.a);//20 先从本类成员变量找
System.out.println("父类的a:" + super.a);//10 直接从父类成员变量找
//子类与父类的属性不同名,是同一个b
System.out.println("b = " + b);//11 先找局部变量找,没有再从本类成员变量找,没有再从父类找
System.out.println("b = " + this.b);//11 先从本类成员变量找,没有再从父类找
System.out.println("b = " + super.b);//11 直接从父类局部变量找
}
public void method(int a, int b){
//子类与父类的属性同名,子类对象中就有两个成员变量a,此时方法中还有一个局部变量a
System.out.println("局部变量的a:" + a);//30 先找局部变量
System.out.println("子类的a:" + this.a);//20 先从本类成员变量找
System.out.println("父类的a:" + super.a);//10 直接从父类成员变量找
System.out.println("b = " + b);//13 先找局部变量
System.out.println("b = " + this.b);//11 先从本类成员变量找
System.out.println("b = " + super.b);//11 直接从父类局部变量找
}
}
class Test{
public static void main(String[] args){
Son son = new Son();
son.test();
son.method(30,13);
}
}
总结:起点不同(就近原则)
-
变量前面没有super.和this.
-
在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的==局部变量==,
-
如果不是局部变量,先从当前执行代码的==本类去找成员变量==
-
如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量==(权限修饰符允许在子类中访问的)
-
-
变量前面有this.
-
通过this找成员变量时,先从当前执行代码的==本类去找成员变量==
-
如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量(==权限修饰符允许在子类中访问的)
-
-
变量前面super.
-
通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
-
如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
-
1.5 解决成员方法重写后调用问题
-
如果子类没有重写父类的方法,只有权限修饰符运行,在子类中完全可以直接调用父类的方法;
-
如果子类重写了父类的方法,在子类中需要通过super.才能调用父类被重写的方法,否则默认调用的子类重写的方法
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test();
Daughter d = new Daughter();
d.test();
}
}
class Father{
protected int num = 10;
public int getNum(){
return num;
}
}
class Son extends Father{
private int num = 20;
public void test(){
System.out.println(getNum());//10 本类没有找父类,执行父类中的getNum()
System.out.println(this.getNum());//10 本类没有找父类,执行父类中的getNum()
System.out.println(super.getNum());//10 本类没有找父类,执行父类中的getNum()
}
}
class Daughter extends Father{
private int num = 20;
@Override
public int getNum(){
return num;
}
public void test(){
System.out.println(getNum());//20 先找本类,执行本类的getNum()
System.out.println(this.getNum());//20 先找本类,执行本类的getNum()
System.out.println(super.getNum());//10 直接找父类,执行父类中的getNum()
}
}
总结:
-
方法前面没有super.和this.
-
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
-
-
方法前面有this.
-
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
-
-
方法前面有super.
-
从当前子类的直接父类找,如果没有,继续往上追溯
-
2.native关键字
2.1 native的意义
native:本地的,原生的
2.2 native的语法
native只能修饰方法,表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。
JVM内存的管理:
区域名称 | 作用 |
---|---|
程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
方法区 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 |
堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存。 |
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 |
3.final关键字
3.1 final的意义
final:最终的,不可更改的
3.2 final修饰类
表示这个类不能被继承,没有子类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
3.3 final修饰方法
表示这个方法不能被子类重写
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
4.final修饰变量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
public class TestFinal {
public static void main(String[] args){
final int MIN_SCORE = 0;
final int MAX_SCORE = 100;
MyDate m1 = new MyDate();
System.out.println(m1.getInfo());
MyDate m2 = new MyDate(2022,2,14);
System.out.println(m2.getInfo());
}
}
class MyDate{
//没有set方法,必须有显示赋值的代码
private final int year;
private final int month;
private final int day;
public MyDate(){
year = 1970;
month = 1;
day = 1;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
public String getInfo(){
return year + "年" + month + "月" + day + "日";
}
}
后续内容,正在更新...