目录
1.static关键字
- 叫静态,可以修饰成员变量、成员方法
成员变量按照有无static修饰,分为两种:
- 类变量(静态变量):有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享。
- 实例变量:属于每个对象的变量,无static修饰
用static修饰的变量name,在学生对象中都不会放name,name是被每个对象共享的
访问方法:
类名.类变量(推荐)
对象.类变量(不推荐)
Student类:
public class Student {
//静态变量:由static修饰,属于类持有,在内存中只加载一次,被类和类的全部对象共享
static String name;
//实例变量:无static修饰,属于每个对象。
int age;
}
Test:
public class Test {
public static void main(String[] args) {
//目标:理解static成员变量的用法
//1.类名.静态变量(推荐)
Student.name = "袁华";
System.out.println(Student.name);
//对象.静态变量(不推荐)
Student s1 = new Student();
s1.name = "马冬梅";
//对象.静态变量(不推荐)
Student s2 = new Student();
s2.name = "秋雅";
System.out.println(s1.name);//static修饰的静态变量name在内存中只有一个,修改的一直都是一个name
//2.对象.实例变量
//Student.age = 12;//报错!!
s1.age = 23;
s2.age = 18;
System.out.println(s1.age);//23,实例变量是每个对象都有的
}
}
static修饰的静态变量name在内存中只有一个,修改的一直都是一个name
实际上这里的name和age都不应该加static,因为每个学生都有单独的name和age
应用场景:在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义为类变量
应用场景
eg:系统启动后,要求用户类可以记住自己创建了多少个用户对象
我们可以在构造器里写User.number++,因为每次创建一个对象就要调用一次构造器
public class User {
public static int number;//静态变量,只有一份,可以被共享
public User(){
// User.number++;
//注意:访问当前类中的静态变量,前面的类名可以省略
number++;
}
}
public class Test2 {
public static void main(String[] args) {
//目标:搞清楚static修饰成员变量的使用场景
new User();
new User();
new User();
System.out.println("创建了几个对象:" + User.number);
}
}
2.static修饰成员方法
我们一般称类方法为静态方法 (有static修饰的成员方法)
静态方法:由static修饰,属于类持有,建议用类名访问,也可以用对象访问
实例方法:对象的方法,必须用对象触发访问
成员方法执行原理:
Student对象调用实例方法时的原理:
应用场景
类方法最常用的应用场景是做工具类
什么是工具类呢?工具类中的方法都是一些静态方法,每个方法都是用来完成一个功能的,工具类是给开发人员共同使用的。
实例:登录和注册类中要进行生成验证码的功能,每次都写一遍会造成代码的复用,所以我们直接创建一个工具类写一个生成验证码的方法,其他类只需要调用即可
public class itheimaUtil {
//将构造器私有,不能再对工具类创建对象
private itheimaUtil(){
}
//静态方法,可以直接通过类名调用,不需要创建对象再调用,节省内存
public static String createCode(int n){
String code = "";
String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random r = new Random();
for (int i1 = 0; i1 < n; i1++) {
int index = r.nextInt(data.length());
char ch = data.charAt(index);
code += ch;
}
return code;
}
}
工具类中的方法为什么用静态方法,不用实例方法?
- 实例方法需要创建对象进行调用,只能创建工具类的对象然后才能调用,此时创建这个对象也只是为了调用方法,而我们完全可以不创建对象直接用类名调用静态方法,创建对象会占内存,会造成内存的浪费
- 静态方法,直接用类名调用即可,调用方便,也能节省内存
工具类没有创建对象的需求,我们应该将工具类的构造器进行私有。
3.static注意事项
public class Test {
//静态变量
public static String schoolName = "我是bbb";
//静态方法
public static void inAddr(){
System.out.println("我现在正在学java~~");
}
//实例变量
private String name;
//实例方法
public void printInfo(){
System.out.println("名字叫:" + name);
}
public static void main(String[] args) {
//目标:理解static的注意事项
Test t = new Test();
t.testNostatic();
}
//1.静态方法中可以直接访问类的静态成员,不可以直接访问实例成员(可以创建对象来访问)
public static void testStatic() {
System.out.println(schoolName);
inAddr();
//Test t = new Test();
//System.out.println(this);//报错,没有对象调它
//System.out.println(name);//报错
//printInfo();//报错,类的实例成员和实例方法属于类的对象,只能创建对象之后调用
}
//2.实例方法中既可以直接访问静态成员,也可以直接访问实例成员
public void testNostatic(){
System.out.println(schoolName);
inAddr();
System.out.println(this.name);//能访问是因为实例方法也要用对象来调
this.printInfo();//3.实例方法中可以出现this关键字,静态方法中不可以出现
}
}
4.代码块
静态代码块
import java.util.ArrayList;
public class CodeTest1 {
public static String schoolName = "学java的bb";
//要定义一个静态的集合来储存一个班级所有人的名字,在一个项目中只有一个这样的集合
public static ArrayList<String> names = new ArrayList<>();
//静态代码块:由static修饰,属于类持有,与类一起优先加载,自动执行一次
//作用:可以用来初始化静态变量的数据
static {
System.out.println("==static静态代码块执行了==");
names.add("张麻子");
names.add("叶飞");
names.add("bbbb");
}
public static void main(String[] args) {
//目标:搞清楚静态代码块的特点,了解其应用场景
System.out.println("===main方法执行了==");
// names.add("张麻子");//也可以在main方法中进行初始化,但是有些方法会反复调用,会造成集合的多次初始化
// names.add("叶飞");//要保证只初始化一次,只能在代码块中初始化
// names.add("bbbb");
System.out.println(names);
}
}
实例代码块
实例可以理解为:实例 == 对象
import java.util.ArrayList;
public class CodeTest2 {
private String name;
//创建一个对象集合,属于这个对象
private ArrayList<String> names = new ArrayList<>();
//实例代码块:属于类的每个对象的,每次创建对象都会执行一次,而且是在构造器之前执行的
//作用:实例变量的初始化
{
System.out.println("===实例代码块执行了一次===");
//name = "dlei";//初始化实例变量,无意义,不可能所有对象的name都是dlei
//names.add("东南");
//names.add("西北");//但这样会导致所有对象集合都存储的是东南、西北,没有意义
}
public CodeTest2(){
System.out.println("====构造器执行了一次====");
}
public static void main(String[] args) {
//目标:搞清楚实例代码块的作用,使用场景
//实例 == 对象
new CodeTest2();
new CodeTest2();
new CodeTest2();
}
}
实际这两种代码块使用并不常见,我们只需要理解:
静态代码块跟着类走,而实例代码块跟着对象走
5.单例设计模式 
单例设计模式确保一个类只有一个对象
写法:
- 把类的构造器私有
- 定义一个类变量记住类的对象
- 定义一个类方法,返回对象
饿汉式单例设计模式
public class A {
//2.定义一个静态变量用于记住类的一个唯一对象
private static A a = new A();//静态变量只会加载一次,所以就只会new一个A对象
//但是用public修饰不安全,应该用private进行封装,让对象不能随意被修改
//public static String name = "黑马";//对比理解
//1.把类的构造器私有化处理
//如果不私有构造器,可以在Test类中随意创建多个对象
//但是构造器私有之后一个对象也无法创建,单例至少要有一个对象
private A(){
}
//3.提供一个静态方法返回这个唯一的对象
public static A getInstance(){
return a;
}
}
public class Test {
public static void main(String[] args) {
//目标:实现单例设计模式
//A a = null;//静态变量是public修饰的不安全,可以随意修改为null
//A a1 = A.a;
//A a2 = A.a;
//System.out.println(a1);
//System.out.println(a2);
A a1 = A.getInstance();
A a2 = A.getInstance();
System.out.println(a1);
System.out.println(a2);
}
}
单例设计模式中一个类只能创建一个对象,但是为什么有a1和a2两个对象呢,实际上这两个对象返回的是一个对象,那就是在A类中创建的a对象,我们也可以用sout(a1 == a2);来验证,返回结果为true
单例设计模式有很多,上面这种方式叫做饿汉式单例
饿汉式单例:拿对象时,对象早就创建好了
懒汉式单例:拿对象时,才开始创建对象
懒汉式单例设计模式
拿对象时,才开始创建对象
写法:
- 把类的构造器私有
- 定义一个类变量用于存储对象
- 提供一个类方法,保证返回的是同一个对象
public class B {
//2.定义一个静态变量用来记住类的一个唯一对象
//不能写private static B b = new B();//这样就又变成了饿汉式单例
private static B b;//此时b == null 懒汉式单例不能从一开始就创建对象
//1.私有构造器
private B(){
}
//3.提供一个静态的get方法,返回一个唯一的对象
public static B getInstance(){
//第一次来拿对象的时候,是需要创建对象的,后面不创建的
if (b == null) {
//第一次拿对象,需要自己创建对象
b = new B();
}
return b;
}
}
package com.itheima.d6_static_sginleinstance;
public class Test2 {
public static void main(String[] args) {
B b1 = B.getInstance();
B b2 = B.getInstance();
System.out.println(b1);
System.out.println(b2);
}
}
执行过程:
第一次调用B.getInstance()时:
- b初始值为null(因为静态变量默认初始值为null)
- 进入if(b == null)条件分支,执行b = new B()创建一个新的B实例
- 返回这个新创建的实例给b1
第二次调用B.getInstance()时:
b
已经不再是null
(因为第一次调用时已经创建了实例并赋值给b
)。- 不会进入
if (b == null)
条件分支,直接返回已存在的实例b
给b2
。虽然看起来创建了两个对象(
b1
和b2
),但实际上它们引用的是同一个实例。
原则:频繁用的对象定义为饿汉式,不频繁用的定义为懒汉式。
6.继承
父类A:
public class A {
public int i;
public void print1(){
System.out.println("===print1===");
}
private int j;
private void print2(){
System.out.println("===print1===");
}
}
子类B:
public class B extends A{
private int k;
public void print3(){
//继承特点:子类中只能继承父类非私有的成员
System.out.println(i);
print1();
//System.out.println(j);//报错
//print2();//报错
}
}
Test:
public class Test {
public static void main(String[] args) {
//目标:认识继承,搞清楚继承的特点,以及继承后类创建对象的特点
//子类对象的创建特点,会由子类和父类等多张设计图共同创建出子类对象,但是能访问什么还是要看权限
B b = new B();//创建的子类对象拥有子类和父类中的所有方法和成员变量(无论私有和公有)
System.out.println(b.i);
//System.out.println(b.j);//b对象里虽然有j,但是不能访问
//System.out.println(b.k);//b对象里虽然有k,但是不能访问,要看权限
b.print1();
//b.print2();
b.print3();
}
}
子类对象的创建特点:会由子类和父类等多张设计图共同创建出子类对象,但是能访问什么还是要看权限
创建的子类对象拥有子类和父类中的所有方法和成员变量(无论是否私有),但能否访问要看权限
内存原理:
7.继承的相关注意事项
7.1权限修饰符
什么是权限修饰符?
就是用来限制类中的成员(成员变量、成员方法、构造器、代码块...)能够被访问的范围
private < 缺省 < protected < public
注意:在不同包下创建一个子类对象来调用方法private、缺省、protected会报错
7.2单继承
java是单继承的,java中的类不支持多继承,但是支持多层继承
public class Test {
public static void main(String[] args) {
//目标:搞清楚继承的特点
}
}
//1.java是单继承的:一个类只能继承一个直接父类
//2.java不支持多继承
//calss A{}
//calss B{}
//calss C entends A,B{}//报错
//3.java支持多层继承
class M{}
class N extends M{}
class P extends N{}
如果可以多继承,那么两个父类中的同名方法会造成子类不知道该继承哪一个
7.3 Object
M类并没有继承任何类,也没有写任何的方法,为什么还是会出现这么多方法
是因为M类其实自己默认继承了Object类,他是所有类的祖宗类
Java中所有类都继承了Object
class M extends Object{}
7.4 方法重写
写一个父类Animal:
public class Animal {
public void run(){
System.out.println("动物会跑~~~");
}
}
再写一个Tiger继承Animal:
public class Tiger extends Animal{
//方法重写:名称与参数列表必须与父类被重写的方法一样
@Override //重写的校验注解:安全,优雅(提示这是重写的代码)
public void run(){
System.out.println("老虎跑的非常快~~~");
}
}
Test类:
public class Test {
public static void main(String[] args) {
//目标:认识方法重写
Tiger t = new Tiger();
t.run();
}
}
使用@Override注解进行重写方法格式的校验,更加安全并提高代码可读性
静态方法是属于类本身持有的 ,但继承是过继给对象的,要用的话直接用类名来调用静态方法
7.5 子类成员的访问特点
在变量体现如下:
下面的代码在实际开发中应该分三个类,这里为了方便观看写到了一个类里面
public class Test {
public static void main(String[] args) {
//目标:继承后子类访问成员的特点,就近原则
Zi zi = new Zi();
zi.showName();
}
}
class Zi extends Fu{
String name = "子类名称";
public void showName(){
String name = "成员变量";
System.out.println(name);//访问子类的局部name
System.out.println(this.name);//访问子类的成员name
System.out.println(super.name);//访问父类的成员name
}
}
class Fu{
String name = "父类名称";
}
方法中体现如下:
public class Test2 {
public static void main(String[] args) {
//目标:继承后子类访问成员的特点,就近原则
Zi2 z = new Zi2();
z.go();
}
}
class Zi2 extends Fu2{
@Override
public void run(){
System.out.println("子类跑的贼溜~~");
}
//中转方法
public void go(){
run();//子类的
super.run();//必须调用父类的
}
}
class Fu2{
public void run(){
System.out.println("父类跑");
}
}
总结一下,在子类中访问成员(成员变量、成员方法)遵循就近原则,子类没有找父类,父类没有就报错
如果子父类中出现了重名的成员,此时一定要在在子类中使用父类的成员应该用:
super.父类成员变量/父类成员方法(只能在子类中使用)
7.6 方法重写的应用场景
public class Student {
private String name;
private char sex;
private double height;
private String desc;
public Student() {
}
public Student(String name, char sex, double height, String desc) {
this.name = name;
this.sex = sex;
this.height = height;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex=" + sex +
", height=" + height +
", desc='" + desc + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
//目标:搞清楚方法重写在实际开发应用中的场景
Student s = new Student("赵敏",'女',169.5,"执着,真诚");
System.out.println(s);
//System.out.println(s.toString());//输出对象,其实默认是调用继承自父类Object的toString来返回所谓的地址
}
}
如果在Student类中不重写toString,当输出Student对象s的时候输出的就是所谓的地址值,默认继承自Object父类,但是我们可以用快捷键重写toString方法来输出对象的属性。
7.7 子类构造器的特点
- 子类的全部构造器,都会先调用父类的构造器,再执行自己。
父类Animal
public class Animal {
public Animal(){
System.out.println("===父类Animal的无参构造器被执行了");
}
public Animal(String n){
System.out.println("===父类Animal的有参构造器被执行了");
}
}
子类Wolf:
public class Wolf extends Animal{
public Wolf(){
super();//写不写都有,调用父类无参构造器
System.out.println("====子类Wolf的无参构造器被执行了===");
}
public Wolf(String n){
super();//写不写都有,调用父类有参构造器
System.out.println("====子类Wolf的you参构造器被执行了===");
}
}
Test:
public class Test {
public static void main(String[] args) {
//目标:子类构造器的特点,都会先执行父类的构造器,再执行自己
Wolf f = new Wolf();
Wolf f1 = new Wolf("我是狼");
}
}
如果把父类Animal中的无参构造器删除,子类无参和有参构造器都会报错,因为子类的有参和无参构造器都一定要调用父类的无参构造器。
如果父类中必须只有有参构造器,没有无参构造器,那么在子类Wolf的有参构造器中就一定要写super(n),指定去调用父类的有参构造器。
7.8 子类访问父类构造器
为什么子类的构造器都要先调用父类的构造器?
是为了把继承自父类的数据先初始化,再把自己的数据初始化
我们通过一个例子来看一下:
People父类:
public class People {
private String name;
private int age;
public People() {
}
public People(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;
}
}
子类Teacher:
public class Teacher extends People{
private String skill;
public Teacher(){
}
public Teacher(String name, int age, String skill) {
super(name,age);
//this.name = name;//报错
//this.age = age;//报错
this.skill = skill;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
Test:
public class Test {
public static void main(String[] args) {
//目标:子类为什么一定要调用父类的构造器,初始化继承自父类部分的数据
Teacher t1 = new Teacher();
t1.setName("波妞");
t1.setAge(35);
t1.setSkill("java,嵌入式");
System.out.println(t1.getName());
System.out.println(t1.getAge());
System.out.println(t1.getSkill());
Teacher t2 = new Teacher("波仔",15,"java,吹牛逼");
System.out.println(t2.getName());
System.out.println(t2.getAge());
System.out.println(t2.getSkill());
}
}
我们可以通过set和get方法,为对象添加或获取属性值,但是这种方法对于对象有很多属性的情况会很麻烦,所以我们可以用到构造器给对象的属性赋值,但是我们会发现下面的情况:
这是因为Teacher子类中并没有name和age属性,而是从父类People中继承的,所以这时候就体现出构造器先调用父类构造器的好处,我们可以在子类的有参构造器中写super(name,age)来调用父类中的有参构造器,来给Teacher对象封装数据。
内存原理:
7.9 this调用兄弟构造器
- 任意类的构造器中,是可以通过this()去调用该类的其他构造器的
public class Student {
private String name;
private int age;
private String schoolName;
public Student() {
}
public Student(String name, int age) {
//this调用兄弟构造器
this(name,age,"清华大学");
}
public Student(String name, int age, String schoolName) {
this.name = name;
this.age = age;
this.schoolName = schoolName;
}
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 getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
}
public class Test {
public static void main(String[] args) {
//目标:this调用兄弟构造器的作用
//1.创建学生对象封装数据
Student s1 = new Student("孙悟空" , 500,"菩提洞");
System.out.println(s1.getName());
System.out.println(s1.getAge());
System.out.println(s1.getSchoolName());
//需求:只要不给学校,学校默认是清华的
//注意事项:
//this(...) super(...) 不能同时出现,且必须在构造器的第一行
//如果同时出现会造成调用两次父类构造器
//必须在第一行是为了保证先去调用兄弟构造器中的super()去调用父类构造器
Student s2 = new Student("蜘蛛精" , 900);
System.out.println(s2.getName());
System.out.println(s2.getAge());
System.out.println(s2.getSchoolName());
}
}
注意事项:
this(...) super(...) 不能同时出现,且必须在构造器的第一行
如果同时出现会造成调用两次父类构造器
必须在第一行是为了保证先去调用兄弟构造器中的super()去调用父类构造器