1、类和对象的概念
java是面向对象的编程语言,包括封装、继承、多态、接口等概念,比面向过程的语言更加符合人的思想行为的语言,也是目前最受欢迎的语言。其中封装、继承和多态为java三大特征。在面向对象语言中,最基础的概念就是类和对象,如下:
类:描述一类对象的行为和状态,如人或手机共同特征 在java语言中写一个类,包括成员变量和成员方法,如下所示 | 对象:对象是类的一个具体实例,有状态(成员变量)和行为(成员方法),具有类的共性中的差异状态和行为。如每台手机都有牌子,但名称不一样。 创建对象方式:类名 对象名 = new 类名(); 如下所示 |
/* (生活中)手机事物: 属性:品牌,价格,颜色... 行为:打电话,发短信,玩游戏... (java语言中)手机类: 成员变量:品牌,价格,颜色 成员方法:打电话,发短信,玩游戏 */ class Phone { //java语言中的手机类 //品牌 String brand; //价格 int price; //颜色 String color; //打电话的方法 public void call(String name) { System.out.println("给"+name+"打电话"); } //发短信的方法 public void sendMessage() { System.out.println("群发短信"); } //玩游戏的方法 public void playGame() { System.out.println("玩游戏"); } } | class PhoneDemo { public static void main(String[] args) { //创建手机对象 //类名 对象名 = new 类名(); Phone p = new Phone(); //这里的对象是类的实例化,可以设置对象具体的参数,手机具体牌子、颜色、价钱和打电话操作等等 //直接输出成员变量值 System.out.println(p.brand+"---"+p.price+"---"+p.color); //给成员变量赋值 p.brand = "诺基亚"; p.price = 100; p.color = "灰色"; //再次输出 System.out.println(p.brand+"---"+p.price+"---"+p.color); //调用方法 p.call("林青霞"); p.sendMessage(); p.playGame(); } } |
1.1、一个对象的内存图
首先main方法先进栈,当new对象时,在堆中创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次,而且方法只有调用时才会执行。
刚开始堆中new出来的成员变量都是默认值的(根据不同的类型为0或null),在栈有Phone p对象对堆中new Phone()地址的引用,通过栈中的引用p.price=3999给堆中的存储空间赋值。p.call(“乔布斯”)方法在栈中随着方法调用完毕而自动销毁,但堆中的new Phone()是由Java虚拟机的自动垃圾回收器来管理,销毁的时间较方法久且随意性。
但是若需要主动释放对象,则在类中可以声明finalize()方法,finalize()方法也叫析构方法,当系统销毁对象时,将自动执行finalize()方法,对象也可以调用finalize()方法销毁自己。finalize()方法无参数,无返回值,基本格式如下:
public void finalize(){ 方法体; } |
1.2、构造方法
类中有一种特殊的成员方法,其方法名与类名相同,称为构造方法(也称构造器)。构造方法的作用是:当new一个新对象时,系统将自动调用构造方法初始化该对象的数据。
格式: A:方法名与类名相同 B:没有返回值类型,连void都没有 C:没有具体的返回值 构造方法的注意事项: A:如果我们没有给出构造方法,系统将自动提供一个无参构造方法。 B:如果我们给出了构造方法,系统将不再提供默认的无参构造方法。 注意:这个时候,如果我们还想使用无参构造方法,就必须自己给出。建议永远自己给出无参构造方法 给成员变量赋值有两种方式: A:setXxx() B:构造方法 |
构造方法的案例: |
class Student { private String name; private int age; public Student() { System.out.println("这是无参构造方法"); //这里的println打印,到对象调用到的带void方法时,会先打印这里内容再打印对象void方法的内容 } //构造方法的重载格式 public Student(String name) { System.out.println("这是带一个String类型的构造方法"); this.name = name; } public Student(int age) { System.out.println("这是带一个int类型的构造方法"); this.age = age; } public Student(String name,int age) { System.out.println("这是一个带多个参数的构造方法"); this.name = name; this.age = age; } public void show() { System.out.println(name+"---"+age); } } class test { public static void main(String[] args) { //创建对象 Student s = new Student(); //调用无参构造方法 s.show(); System.out.println("-------------"); //创建对象2 Student s2 = new Student("林青霞"); s2.show(); System.out.println("-------------"); //创建对象3 Student s3 = new Student(27); s3.show(); System.out.println("-------------"); //创建对象4 Student s4 = new Student("林青霞",27); //调用有参构造方法 s4.show(); } } //以下截取部分结果(无参构造方法的s.show()调用的结果): 这是无参构造方法 null---0 ------------- |
1.3、形式参数是类名的问题
/* 形式参数的问题: 基本类型:形式参数的改变不影响实际参数 引用类型:形式参数的改变直接影响实际参数 */ //形式参数是基本类型 class Demo { public int sum(int a,int b) { return a + b; } } //形式参数是引用类型 class Student { public void show() { System.out.println("我爱学习"); } } class StudentDemo { //如果你看到了一个方法的形式参数是一个类类型(引用类型),这里其实需要的是该类的对象。 public void method(Student s) { //调用的时候,把main方法中的s的地址传递到了这里 Student s = new Student(); s.show(); } } class ArgsTest { public static void main(String[] args) { //形式参数是基本类型的调用 Demo d = new Demo(); int result = d.sum(10,20); System.out.println("result:"+result); System.out.println("--------------"); //形式参数是引用类型的调用 //需求:我要调用StudentDemo类中的method()方法 StudentDemo sd = new StudentDemo(); //创建学生对象 Student s = new Student(); sd.method(s); //把s的地址给到了这里 } } |
1.4、匿名对象
/* 匿名对象:就是没有名字的对象。 匿名对象的应用场景: A:调用方法,仅仅只调用一次的时候。 注意:调用多次的时候,不适合。 那么,这种匿名调用有什么好处吗? 有,匿名对象调用完毕就是垃圾。可以被垃圾回收器回收。 B:匿名对象可以作为实际参数传递 */ class Student { public void show() { System.out.println("我爱学习"); } } class StudentDemo { public void method(Student s) { s.show(); } } class NoNameDemo { public static void main(String[] args) { //带名字的调用 Student s = new Student(); s.show(); s.show(); System.out.println("--------------"); //匿名对象 //new Student(); //匿名对象调用方法 new Student().show(); new Student().show(); //这里其实是重新创建了一个新的对象 System.out.println("--------------"); //匿名对象作为实际参数传递 StudentDemo sd = new StudentDemo(); //Student ss = new Student(); //sd.method(ss); //这里的s是一个实际参数 //匿名对象 sd.method(new Student()); //在来一个 new StudentDemo().method(new Student()); } } |
1.5 创建对象时内存的过程和图解
Student s = new Student();在内存中做了哪些事情?
(1)加载Student.class文件进内存
(2)在栈内存为s开辟空间
(3)在堆内存为学生对象开辟空间
对学生对象的成员变量进行默认初始化 //null 0
对学生对象的成员变量进行显示初始化 //林青霞 27
通过构造方法对学生对象的成员变量赋值 //刘意 30
(4)学生对象初始化完毕,把对象地址赋值给s变量 //0x0001
2、类的封装
类的封装性原则:即要提供类与外部的联系,又要尽可能隐藏类的实现细节。
封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。就是说我可以提供功能给你用,但是你不需要了解我怎样实现的,这在生产环境中至关重要,有牵扯到一些技术专利等问题,同时封装也提供一定程度的安全性。
封装的好处: |
A:隐藏实现细节,提供公共的访问方式 B:提高代码的复用性 C:提高代码的安全性 |
类的封装性是通过为类及成员变量和成员方法分别设置合理的访问权限实现的。
2.1、访问权限
在java中共有4种访问权限,如下图:
权限修饰符 | 本类 | 本类所在包 | 其他包中子类 | 其他包中非子类 |
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
默认 | √ | √ | × | × |
private | √ | × | × | × |
由上图可知,public是公共类,protected为是继承的子类权限设计的,默认是同一个包中的类有互相访问权限,private只限本类访问。
简单的java封装案例: |
class Demo { //int num = 10; //用private修饰 private int num = 10; public void show() { System.out.println(num); } private void method() { System.out.println("method"); } public void function() { method(); } } class PrivateDemo { public static void main(String[] args) { Demo d = new Demo(); //不能方法私有的成员变量 //System.out.println(d.num); d.show(); //不能访问私有的成员方法 //d.method(); d.function(); //虽然在main方法中不能直接访问私有的成员变量、成员方法,但可以通过Demo类的public修饰的成员方法间接访问Demo类的private修饰的成员变量、成员方法 } } |
由上代码可知,在同一个类中,通过public修饰的成员方法间接访问private修饰的成员变量、成员方法的过程就是封装的体现。由此也引出了任何一个实体类,带有了getXxx()和setXxx()的成员方法,通过setXxx()方法设置类中private的成员变量的值,而getXxx()方法可以得到private的成员变量的值。
java的getXxx()和setXxx()方法的封闭案例: |
/* 封装和private的应用: A:把成员变量用private修饰 B:提高对应的getXxx()和setXxx()方法 */ //定义学生类 class Student { //姓名 private String name; //年龄 private int age; //姓名获取值 public String getName() { return name; } //姓名设置值 public void setName(String n) { name = n; } //年龄获取值 public int getAge() { return age; } //年龄赋值 public void setAge(int a) { age = a; } } //测试类 class StudentTest { public static void main(String[] args) { //创建学生对象 Student s = new Student(); //使用成员变量 //错误:被私有修饰了,外界不能直接访问了 //System.out.println(s.name+"---"+s.age); System.out.println(s.getName()+"---"+s.getAge()); //给成员变量赋值 //s.name = "林青霞"; //s.age = 27; //通过方法给赋值 s.setName("林青霞"); s.setAge(27); System.out.println(s.getName()+"---"+s.getAge()); } } |
从上代码看出,都是 getName() 或 setName() 成员方法返回或赋值给Student类的成员变量name,但如果在getName()或setName() 的成员方法中定义多一个局部变量num时,就会容易出问题了。因为方法的局部变量优先级>类的成员变量的,在同名的情况下,变量名相同,对象是先取方法中的局部变量的值,而不是对象的成员变量的值。在特殊情况下,要把对象的成员变量和方法的局部变量区别出来,这时引进了this关键字。
3、this关键字
this关键字是指当前类的对象引用。简单的记,它就代表当前类的一个对象。解决局部变量隐藏成员变量。
上一案例代码的改进版:(引进了this,更加严谨) |
/* this:哪个对象调用那个方法,this就代表那个对象 */ class Student { private String name; private int age; public String getName() { return name; //这里其实是隐含了this } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } class StudentTest2 { public static void main(String[] args) { //创建一个对象 Student s1 = new Student(); s1.setName("林青霞"); s1.setAge(27); System.out.println(s1.getName()+"---"+s1.getAge()); //创建第二个对象 Student s2 = new Student(); s2.setName("刘意"); s2.setAge(30); System.out.println(s2.getName()+"---"+s2.getAge()); } } |
下面,再来看下,通过this关键字,区分调用同名的局部变量和成员变量
区分this.num和num的案例: |
class getNum { private String name; private int age; int num=11; //成员变量 public void show() { int num=22; //局部变量 System.out.println("num: "+num); System.out.println("this.num: "+this.num); } } class test { public static void main(String[] args) { getNum t1 = new getNum(); t1.show(); } } /* 结果是: num: 22 this.num: 11 */ |
所以this关键字可以理解为调用的是当前的对象,可以随意调用当前对象任意的成员变量(包括private修饰的,如this.num、this.name和this.age)。以后再写一个类的时候,把所有的成员变量给private了,提供对应的getXxx()/setXxx()方法。
4、static关键字
static关键字实现多个对象有共同的成员变量值的,表静态的,共享的。
4.1、static的特点
/* static的特点:(它可以修饰成员变量,还可以修饰成员方法) A:随着类的加载而加载 回想main方法。 B:优先于对象存在 C:被类的所有对象共享 举例:咱们班级的学生应该共用同一个班级编号。 其实这个特点也是在告诉我们什么时候使用静态? 如果某个成员变量是被所有对象共享的,那么它就应该定义为静态的。 举例: 饮水机(用静态修饰) 水杯(不能用静态修饰) D:可以通过类名调用 其实它本身也可以通过对象名调用。 推荐使用类名调用。 静态修饰的内容一般我们称其为:与类相关的,类成员 */ class Student { //非静态变量 int num = 10; //静态变量 static int num2 = 20; } class StudentDemo { public static void main(String[] args) { Student s = new Student(); System.out.println(s.num); System.out.println(Student.num2); System.out.println(s.num2); } } |
4.2、static关键字的注意事项
/* static关键字注意事项 A:在静态方法中是没有this关键字的 如何理解呢? 静态是随着类的加载而加载,this是随着对象的创建而存在。 静态比对象先存在。 B:静态方法只能访问静态的成员变量和静态的成员方法 静态方法: 成员变量:只能访问静态变量 成员方法:只能访问静态成员方法 非静态方法: 成员变量:访问对象可以是静态的,也可以是非静态的 成员方法:访问对象可是是静态的成员方法,也可以是非静态的成员方法。 简单记: 静态只能访问静态。 */ class Teacher { public int num = 10; public static int num2 = 20; public void show() { System.out.println(num); //隐含的告诉你访问的是成员变量 System.out.println(this.num); //明确的告诉你访问的是成员变量 System.out.println(num2); //function(); //function2(); } public static void method() { //无法从静态上下文中引用非静态 变量 num //System.out.println(num); System.out.println(num2); //无法从静态上下文中引用非静态 方法 function() //function(); function2(); } public void function() { } public static void function2() { } } class TeacherDemo { public static void main(String[] args) { //创建对象 Teacher t = new Teacher(); t.show(); System.out.println("------------"); t.method(); } } |
4.3、main方法格式讲解
/* main方法的格式讲解: public static void main(String[] args) {...} public:公共的,访问权限是最大的。由于main方法是被jvm调用,所以权限要够大。 static:静态的,不需要创建对象,通过类名就可以。方便jvm的调用。 void:因为我们曾经说过,方法的返回值是返回给调用者,而main方法是被jvm调用。你返回内容给jvm没有意义。 main:是一个常见的方法入口。我见过的语言都是以main作为入口。 String[] args:这是一个字符串数组。值去哪里了? 这个东西到底有什么用啊?怎么给值啊? 这个东西早期是为了接收键盘录入的数据的。 格式是: java MainDemo hello world java */ class MainDemo { public static void main(String[] args) { //System.out.println(args); //[Ljava.lang.String;@175078b //System.out.println(args.length); //0 //System.out.println(args[0]); //ArrayIndexOutOfBoundsException //接收数据后 System.out.println(args); System.out.println(args.length); //System.out.println(args[0]); for(int x=0; x<args.length; x++) { System.out.println(args[x]); } } } |
4.4、static关键字的内存图
在static关键字时,在内存中我们引入了方法区的概念,方法区和堆都能够被所有线程所共享的,方法区包含所有的class和static变量。如下图:
static的内存图解 |
|
5、代码块
代码块:在Java中,使用{}括起来的代码被称为代码块。
代码块的分类:(根据其位置和声明的不同) |
1、局部代码块:局部位置,用于限定变量的生命周期(变量出了大括号后就不存在了)。 2、构造代码块:在类中的成员位置,用{}括起来的代码。每次调用构造方法执行前,都会先执行构造代码块。构造代码块可以把多个构造方法中的共同代码放到一起,对对象进行初始化。 3、 静态代码块:在类中的成员位置,用{}括起来的代码,只不过它用static修饰了。静态代码块一般是对类进行初始化。 |
面试题? 静态代码块,构造代码块,构造方法的执行顺序? 静态代码块 --> 构造代码块 --> 构造方法 静态代码块只执行一次 构造代码块每次调用构造方法都执行 |
代码块案例:看程序写结果 |
class Code { static { int a = 1000; System.out.println(a); } //构造代码块 { int x = 100; System.out.println(x); } //构造方法 public Code(){ System.out.println("code"); } //构造方法 public Code(int a){ System.out.println("有参code"); } //构造代码块 { int y = 200; System.out.println(y); } //静态代码块 static { int b = 2000; System.out.println(b); } } class CodeDemo { public static void main(String[] args) { //局部代码块 { int x = 10; System.out.println(x); } //找不到符号 //System.out.println(x); { int y = 20; System.out.println(y); } System.out.println("---------------"); Code c = new Code(); System.out.println("---------------"); Code c2 = new Code(); System.out.println("---------------"); Code c3 = new Code(1); } } //根据上面代码块知识点先思考再看下面答案 |
结果如下: |
10 20 --------------- 1000 2000 100 200 code --------------- 100 200 code --------------- 100 200 有参code |
代码块案例2:看程序写答案 class Student { static { System.out.println("Student 静态代码块"); } { System.out.println("Student 构造代码块"); } public Student() { System.out.println("Student 构造方法"); } } class StudentDemo { static { System.out.println("中国队赢了,我很开心"); } public static void main(String[] args) { System.out.println("我是main方法"); Student s1 = new Student(); Student s2 = new Student(); } } |
结果如下: 中国队赢了,我很开心 我是main方法 Student 静态代码块 Student 构造代码块 Student 构造方法 Student 构造代码块 Student 构造方法 |