因为count为静态成员变量,其存储在方法区中且只有一个,是共享的,所以每次当访问它时,都会影响它的值
3.3.2修饰方法
static关键字修饰方法,此方法称为静态成员方法,同时也成为类方法
1.访问方式:通过类名.静态成员方法名(),同时需要注意的是通过对象的引用也是可以访问静态成员变量的,虽然在书写时编译器(idea)可能会报错,但是运行时是不会报错的。
2.特点:
静态方法属于类,而不属于类的对象。
可以直接调用静态方法,而无需创建类的实例。
静态方法可以访问静态数据成员,并可以更改静态数据成员的值。
代码示例:
class TestDemo {
public int a;
public static int count;
public static void change() {
count = 100;
//a = 10; error 不可以访问非静态数据成员
}
}
public class Main{
public static void main(String[] args) {
TestDemo.change();//无需创建实例对象 就可以调用
System.out.println(TestDemo.count);
}
**注意事项:**静态方法和实例无关, 而是和类相关. 因此会导致几种情况:
(1): 在静态方法的内部是不能访问实例成员变量的,同样也不能调用实例成员方法
(2): 注意不管是静态的方法还是非静态的方法中都不能定义静态的变量,但是可以定义非静态的
原因:因为静态的变量属于类并不属于方法,也就是我们俗称的类变量
(3):this和super两个关键字不能在静态上下文中使用(this 是当前对象的引用, super是父类对象的引用,而静态是不依赖对象的,所以不能在静态方法中使用this和super关键字).
(4):所有被static所修饰的方法或者属性,全部不依赖于对象。
3.4小结
观察以下代码, 分析内存布局:
class Person {
public int age;//实例变量存放在对象内
public String name;//实例成员变量
public String sex;//实例成员变量
public static int count;//类变量也叫静态成员变量,编译时已经产生,属于类本身,且只有一份。存放在方法区
public final int SIZE = 10;//被final修饰的叫常量,但是也属于对象。 同样存储于堆上,被final修饰,后续不可更改
注意:我们在判断成员变量将来是在方法区还是在堆上,就看其是否被static关键字修饰即可,不用看是否跟final有关,final修饰一个成员变量只代表这个成员变量是一个常量
final修饰的变量是只有当这个变量定义在方法内部,才会存储在栈上,因为这个时候的变量是一个局部变量.
public static final int COUNT = 99;//静态的常量,属于类本身,只有一份且存储在方法区 被final修饰,后续不可更改
//实例成员方法
public void eat() {
int a = 10;//局部变量
System.out.println(“eat()!”);
}
//实例成员方法
public void sleep() {
System.out.println(“sleep()!”);
}
//静态成员方法
public static void staticTest(){
//不能访问非静态成员
//sex = “man”; error
//同时也不能访问实例成员方法
//sleep(); error
System.out.println(“StaticTest()”);
}
}
public class Main{
public static void main(String[] args) {
Person person1 = new Person();
Person person2 = new Person();
Person person3 = new Person();
}
}
数据属性的内存布局:这里只列举一部分属性,大家能看懂如何存储即可。
4.封装
什么叫封装?
封装就是将重要的数据和重要的方法拿private关键字来修饰,修饰之后就变为私有的,也就是只能在类内进行访问
在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了. 这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度.并且提升了数据的安全性
4.1private实现封装****
private/ public 这两个关键字表示 “访问权限控制” .
-
被 public 修饰的成员变量或者成员方法, 可以直接被类的调用者使用.
-
被 private 修饰的成员变量或者成员方法, 不能被类的调用者使用.
换句话说, 类的使用者根本不需要知道, 也不需要关注一个类都有哪些 private 的成员. 从而让类调用者以更低的成本来使用类.
直接使用 public
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 + “, 今年” + person.age + “岁”);
}
}
// 执行结果
我叫张三, 今年18岁
这样的代码导致类的使用者(main方法的代码)必须要了解 Person 类内部的实现, 才能够使用这个类. 使得学习成本较高
一旦类的实现者修改了代码(例如把 name 改成 myName), 那么类的使用者就需要大规模的修改自己的代码, 维护成本较高.
范例:使用 private 封装属性, 并提供 public 方法供类的调用者使用.
class Person {
private String name = “张三”;
private int age = 18;
public void show() {
System.out.println(“我叫” + name + “, 今年” + age + “岁”);
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.show();
}
}
// 执行结果
我叫张三, 今年18岁
此时字段已经使用 private 来修饰. 类的调用者(main方法中)不能直接使用. 而需要借助 show 方法. 此时类的使用者就不必了解 Person 类的实现细节.
同时如果类的实现者修改了字段的名字, 类的调用者不需要做出任何修改(类的调用者根本访问不到 name, age这样的字段).
那么问题来了~~ 类的实现者万一修改了 public 方法 show 的名字, 岂不是类的调用者仍然需要大量修改代码嘛?
这件事情确实如此, 但是一般很少会发生. 一般类的设计都要求类提供的 public 方法能比较稳定, 不应该频繁发生大的改变. 尤其是对于一些基础库中的类, 更是如此. 每次接口的变动都要仔细考虑兼容性问题.
注意事项:
1.private 不光能修饰字段, 也能修饰方法
2.通常情况下我们会把字段设为 private 属性, 但是方法是否需要设为 public, 就需要视具体情形而定. 一般我们希望一个类只提供 “必要的” public 方法, 而不应该是把所有的方法都无脑设为 public.
4.2getter和setter方法****
当我们使用 private 来修饰字段的时候, 就无法直接使用这个字段了.
代码示例:
class Person {
private String name = “张三”;
private int age = 18;
public void show() {
System.out.println(“我叫” + name + “, 今年” + age + “岁”);
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.age = 20;
person.show();
}
}
// 编译出错
Test.java:13: 错误: age可以在Person中访问private
person.age = 20;
^
1 个错误
此时如果需要获取或者修改这个 private 属性, 就需要使用 getter / setter 方法.
代码示例:
class Person {
private String name;//实例成员变量
private int age;
public void setName(String name) {
//name = name;//不能这样写
this.name = name;//this引用,表示调用该方法的对象
}
public String getName(){
return name;
}
public void show(){
System.out.println("name: “+name+” age: "+age);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName(“caocao”);
String name = person.getName();
System.out.println(name);
person.show();
}
// 运行结果
caocao
name: caocao age: 0
注意事项:
1.getName 即为 getter 方法, 表示获取这个成员的值. setName 即为 setter 方法, 表示设置这个成员的值.
2.当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值.此时值是传不过来的,this 表示当前对象的引用.
3.不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.
4.在 IDEA 中可以使用 alt + insert (或者 alt + F12) 快速生成 setter / getter 方法. 在 VSCode 中可以使用鼠标右键菜单 -> 源代码操作 中自动生成 setter / getter 方法.
5.构造方法
5.1基本语法
构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作.
实例化对象的过程分为两步:
1.为对象分配内存空间
2.调用合适的构造方法,合适意味着构造方法并不止一个。
语法规则
- 方法名称必须与类名称相同
- 每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)
- 构造方法没有返回值类型声明
注意事项
1.如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数
2.若类中定义了构造方法,则默认的无参构造将不再生成
3.构造方法支持重载. 规则和普通方法的重载一致.
4.构造函数的作用就是构造对象,同时也可以在构造对象的同时对对象的成员进行初始化
5.不同的构造方法的调用主要是看括号内部参数是什么,就调用相对应的即可
代码示例:
class People {
private String name;
private int age;
public String sex;
public static int count;
//构造方法
People() {
System.out.println(“不带有参数的构造方法”);
}
People(String name) {
System.out.println(“带有1个参数,String的构造方法!”);
}
People(String name, int age) {
System.out.println(“带有2个参数,String,int的构造方法!”);
}
People(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void print() {
System.out.println(“name=” + name + " " + “age=” + age + " " + “sex=” + sex);
}
}
public class LeiHeDuiXiang3 {
public static void main(String[] args) {
//不同的构造方法的调用主要是看括号内部参数是什么,就调用相对应的即可
//输出结果为 System.out.println(“不带有参数的构造方法”);
People person = new People();
//输出结果为 System.out.println(“带有1个参数,String的构造方法!”);
People person1 = new People(“songbiao”);
//输出结果为System.out.println(“带有2个参数,String,int的构造方法!”);
People person2 = new People(“songbiao”, 100);
System.out.println(“==============================================”);
//构造函数的作用就是构造对象,同时也可以在构造对象的同时对对象的成员进行初始化
People person3 = new People(“songbiao”, 100, “男”);
person3.print();
}
}
在这里注意一个点。假若我们此时去掉People()这个任何参数的构造函数的话,当我们new People()的时候程序便会报错,为什么?
****答:因为当我们不定义构造函数时,其实编译器会 默认给我们生成一个无参数的构造函数,而当我们定义了其他的********有参的构造函数而没有定义无参数的构造函数时,此时系统不会再默认为我们定义无参数的构造函数了,那么当我们通过语句new People()****实例化一个对象时,便找不到对应的构造函数了,那么此时需要我们自己定义一个无参数的构造函数。
5.2this关键字(重点)
this表示当前对象引用(注意不是当前对象). 可以借助 this 来访问对象的字段和方法.
在这里讲下为什么this是对象的引用而不是对象?
答:首先来看下实例化一个对象分为几步吧:
第1步:为对象分配内存
第2步:调用合适的构造方法。注意:合适二字意味着构造函数可不止一个
this不是对象的原因是当我们完成对构造方法的调用后对象才会创建,而我们此时能在构造函数中去使用this,则说明this出现在对象创立之前,所以this并不是指对象
当我们在实例化对象的时候,第一步是为对象分配内存,既然有了内存就一定有了地址,地址是存在引用当中的,所以我们说此时this指的是对象的引用。
this的三种使用方式:
1****:this访问成员变量****
2****:this调用成员方法****
3****:this调用构造方法:在一个构造方法内部调用另外一个构造方法****
代码示例:
class People1 {
private String name;
private int age;
public String sex;
public static int count;
//this调用构造方法
People1() {
/*
注意this不能调用本身的构造函数,不然会陷入死循环
this();
*/
/*
this在调用构造方法时只能放在第一行并且只能调用一次构造函数
this在调用构造方法时只能用在构造方法中
*/
this(“songbiao”);
/*
注意此时this如果想要调用第二个构造函数便会报错
this(“haha”,10);
*/
System.out.println(“haha”);
}
People1(String name) {
System.out.println(“hehe”);
}
People1(String name, int age) {
System.out.println(“heheda”);
}
//普通成员方法
public void eat() {
People1 p = new People1();
System.out.println(“吃饭”);
}
/*1.普通的成员方法中是可以使用this来访问实例成员变量和静态成员变量的
2.普通的成员方法中是可以使用this来调用实例成员方法和静态成员方法的
*/
public void print() {
//使用this来访问实例成员变量name,age
System.out.println(“name=” + this.name + " " + “age=” + this.age + " " + “sex=” + sex);
//使用this来访问静态成员变量count,虽然不妥但是编译时不会出错的
System.out.println(this.count);
//使用this来调用实例成员方法eat
this.eat();
//使用this来调用静态成员方法func1,虽然不妥但是编译时不会出错的
this.func1();
//可以直接调用静态成员方法func1
func1();
//可以直接调用实例成员方法eat
eat();
/*
注意不管是静态的方法还是非静态的方法中都不能定义静态的变量,但是可以定义非静态的
原因:因为静态的变量属于类并不属于方法,也就是我们俗称的类变量
static int a=10;(不允许)
*/
//可以直接访问静态成员变量count
count++;
}
public static void func1() {
int a = 10;
System.out.println(“sss”);
}
/*
静态方法中是不能使用this的,原因是this代表对象的引用,而静态是不依赖对象的
*/
public static void func() {
// 静态方法中不能使用this访问实例成员变量或者静态成员变量
//System.out.println(this.name);
//System.out.println(this.count);
//静态方法中不能使用this调用实例成员方法或者静态成员方法
//this.print();
//this.func1();
//静态方法中不能直接调用非静态成员方法
//print();
//静态方法中可以直接调用静态成员方法
func1();
//静态方法中可以直接访问静态成员变量
count++;
/*注意不管是静态的方法还是非静态的方法中都不能定义静态的变量,但是可以定义非静态的
static int a=10;(不允许)
//总结:静态方法中不能访问成员变量,同时也不能调用成员方法,并且不能在静态方法中定义静态变量
*/
System.out.println(“此方法为静态方法”);
}
}
public class LeiHeDuiXiang4 {
public static void main(String[] args) {
People1 person = new People1();
person.eat();
}
}
下面我们来逐个分析:
1:this关键字可以在构造方法中使用,那么我们来看代码:
People1() {
/*
注意this不能调用本身的构造函数,不然会陷入死循环
this();
*/
/*
this在调用构造方法时只能放在第一行并且只能调用一次构造函数
this在调用构造方法时只能用在构造方法中
*/
this(“songbiao”);
/*
注意此时this如果想要调用第二个构造函数便会报错
this(“haha”,10);
*/
System.out.println(“haha”);
}
1.首先我们可以使用this关键字在一个构造方法中去调用另一个构造方法,但是不能调用本身,这样会陷入死循环。
2.this方法在调用其他构造方法时只能将调用语句放在构造方法中的第一行并且只能调用一次。
3.this如果想要调用第二个构造函数便会报错,说明一个构造函数中只能调用一次其他构造函数。
2.this可以在普通成员方法中使用,下面来看代码:
public void print() {
//使用this来访问实例成员变量
System.out.println(“name=” + this.name + " " + “age=” + this.age + " " + “sex=” + sex);
//使用this来访问静态成员变量,虽然不妥但是编译时不会出错的
System.out.println(this.count);
//使用this来调用实例成员方法
this.eat();
//使用this来调用静态成员方法,虽然不妥但是编译时不会出错的
this.func1();
//可以直接调用静态成员方法
func1();
//可以直接调用实例成员方法
eat();
/*
注意不管是静态的方法还是非静态的方法中都不能定义静态的变量,但是可以定义非静态的
原因:因为静态的变量属于类并不属于方法,也就是我们俗称的类变量
static int a=10;(不允许)
*/
//可以直接访问静态成员方法
count++;
}
1.在普通成员方法中可以使用this关键字来访问成员变量,调用成员方法,虽然使用this关键字访问静态成员变量和调用静态成员方法时编译器(idea)会报错,但是运行时是不会出错的。
2.在普通成员方法中也可以直接访问成员变量和成员方法。
3. this不可以在静态成员方法中使用,下面来看代码:
public static void func() {
// 静态方法中不能使用this访问实例成员变量或者静态成员变量
//System.out.println(this.name);
//System.out.println(this.count);
//静态方法中不能使用this调用实例成员方法或者静态成员方法
//this.print();
//this.func1();
//静态方法中不能直接调用非静态成员方法
//print();
//静态方法中可以直接调用静态成员方法
func1();
/*注意不管是静态的方法还是非静态的方法中都不能定义静态的变量,但是可以定义非静态的
static int a=10;(不允许)
//总结:静态方法中不能访问成员变量,同时也不能调用成员方法,并且不能在静态方法中定义静态变量
*/
//静态方法中可以直接访问静态成员变量
count++;
System.out.println(“此方法为静态方法”);
}
}
此时我们会发现this不可以在静态方法中使用,这是为什么呢?
答:因为this代表对象的引用,而静态是不依赖对象的。
所以在静态成员方法中只能访问静态的成员变量以及调用静态成员方法。
在这里再次强调下:注意不管是静态的方法还是非静态的方法中都不能定义静态的变量,但是可以定义非静态的,因为静态的变量属于类并不属于方法,也就是我们俗称的类变量
6.认识代码块
6.1什么是代码块
使用 {} 定义的一段代码.
根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块(了解即可,一般不会用)
- 构造块(实例代码块)
- 静态代码块
- 同步代码块(后续讲解多线程部分再谈)
6.2普通代码块
普通代码块:定义在方法中的代码块.
public class Main{
public static void main(String[] args) {
{
//直接使用{}定义,普通方法块
int x = 10 ;
System.out.println("x1 = " +x);
}
int x = 100 ;
System.out.println("x2 = " +x);
}
}
// 执行结果
x1 = 10
x2 = 100
这种用法较少见。
6.3构造代码块
构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
class Person {
private String name;//实例成员变量private int age;
private String sex;
public Person() {
System.out.println(“I am Person init()!”);
}
//实例代码块
{
this.name = “bit”;
this.age = 12;
this.sex = “man”;
System.out.println(“I am instance init()!”);
}
public void show() {
System.out.println("name: " + name + " age: " + age + " sex: " + sex);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person();
p1.show();
}
}
// 运行结果
I am instance init()!
I am Person init()!
name: bit age: 12 sex: man
注意事项****😗* 实例代码块优先于构造函数执行。**
6.4静态代码块
使用static定义的代码块。一般用于初始化静态成员属性。
****代码示例:****此处思考最终的输出结果是多少呢?
class Person2 {
private String name;//实例成员变量
private int age;
private String sex;
private static int count = 0;//静态成员变量
public Person2() {
System.out.println(“I am Person init()!”);
}
//实例代码块
{
this.name = “bit”;
this.age = 12;
this.sex = “man”;
System.out.println(“I am instance init()!”);
}
//静态代码块
static {
count = 10;//只能访问静态数据成员
System.out.println(“I am static init()!”);
}
public void show() {
System.out.println("name: " + name + " age: " + age + " sex: " + sex);
}
}
public class LeiHeDuiXiang6 {
public static void main(String[] args) {
Person2 p1 = new Person2();
Person2 p2 = new Person2();
}
}
注意事项:
1.静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。
2.静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。
所以我们可以推断出上述代码最终的输出结果为:
I am static init()!
I am instance init()!
I am Person init()!
I am instance init()!
I am Person init()!
此时我们来修改下代码再看两个问题:
class Person2 {
private String name;//实例成员变量
private int age;
private String sex;
private static int count = 0;//静态成员变量
public Person2() {
System.out.println(“I am Person init()!”);
}
//实例代码块
{
this.name = “bit”;
this.age = 12;
this.sex = “man”;
System.out.println(“I am instance init()!”);
}
//静态代码块
static {
count = 10;//只能访问静态数据成员
最后
码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到
又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考
以下是部分内容截图
) {
System.out.println(“I am Person init()!”);
}
//实例代码块
{
this.name = “bit”;
this.age = 12;
this.sex = “man”;
System.out.println(“I am instance init()!”);
}
//静态代码块
static {
count = 10;//只能访问静态数据成员
System.out.println(“I am static init()!”);
}
public void show() {
System.out.println("name: " + name + " age: " + age + " sex: " + sex);
}
}
public class LeiHeDuiXiang6 {
public static void main(String[] args) {
Person2 p1 = new Person2();
Person2 p2 = new Person2();
}
}
注意事项:
1.静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。
2.静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。
所以我们可以推断出上述代码最终的输出结果为:
I am static init()!
I am instance init()!
I am Person init()!
I am instance init()!
I am Person init()!
此时我们来修改下代码再看两个问题:
class Person2 {
private String name;//实例成员变量
private int age;
private String sex;
private static int count = 0;//静态成员变量
public Person2() {
System.out.println(“I am Person init()!”);
}
//实例代码块
{
this.name = “bit”;
this.age = 12;
this.sex = “man”;
System.out.println(“I am instance init()!”);
}
//静态代码块
static {
count = 10;//只能访问静态数据成员
最后
码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到
又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考
以下是部分内容截图
[外链图片转存中…(img-ViroCGNf-1714339084312)]