向着Java程序员的目标前进!
day12
学习内容
面向对象—续2
构造方法
构造方法是一种特殊的方法,作用是给对象的数据进行初始化。
构造方法的特殊性(与一般方法比较):
- 构造方法的方法名与类名相同
- 没有具体的返回值类型,连void都没有
- 没有具体的返回值
与一般方法的联系:可以重载
构造方法使用的注意事项:
-
在构造方法中,没有返回值类型,连void都没有。所以不要随便定义构造方法。
-
一个类如果不是测试类,在没有提供任何构造方法的情况下,系统会默认给我们提供无参构造方法。但是我们在学习了构造方法后,永远要记得定义无参构造方法,因为继承中会用到!
-
如果我们在一个类中给出了有参构造方法,那么系统就不会再提供无参构造方法。当你使用无参构造方法创建对象时,系统就会报错。(这也是永远要记得给出无参构造方法的原因之一)
补充:到现在为止,给成员变量数据初始化由两种方式:
- 通过无参构造方法+setXXX()赋值,通过getXXX()获取值
- 通过有参构造方法直接赋值,getXXX()获取值
构造方法的使用举例
/**
* 需求:创建学生类,属性包括姓名、年龄、性别、身高、爱好;
* 方法包括学习、抽烟
* 创建完毕后进行测试
*/
class Student {
private String name ; //姓名
private int age ; //年龄
private String gender ;//性别
private String hobby ; //爱好
private int high ;//身高
//提供无参构造方法
//快捷键:alt+ins(+fn:自己电脑可能需要一直按上)
// --->constructor--->select none 无参构造
public Student() {
}
//有参构造方法:自己先手写完属性----再使用快捷完成
//alt+ins+(fn:自己电脑可能需要一直按上)
// --->constructor--->全选所有属性赋值
public Student(String name, int age, String gender, String hobby, int high) {
this.name = name;
this.age = age;
this.gender = gender;
this.hobby = hobby;
this.high = high;
}
//提供对外的公共的SetXXX()/getXXX()方法
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 getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public int getHigh() {
return high;
}
public void setHigh(int high) {
this.high = high;
}
//其他的成员方法
//学习,抽烟...
public void study(String className){
System.out.println("正在学习"+className);
}
//返回String
public String smoke(String brandSmoke){//烟的牌子
return "学生抽"+brandSmoke ;
}
}
class StudentTest {
public static void main(String[] args) {
//方式1:无参构造方法+setXXX()/getXXX()
Student student = new Student() ;//系统默认初始化
//显式初始化
student.setName("张三丰") ;
student.setAge(50) ;
student.setGender("男") ;
student.setHigh(180) ;
student.setHobby("太极") ;
System.out.println("这个学生的信息如下:");
System.out.println("姓名\t年龄\t性别\t身高\t爱好");
System.out.println(student.getName()+"\t"+student.getAge()+"\t\t"+student.getGender()+"\t\t"+student.getHigh()+"\t\t"+student.getHobby());
//调用其他方法
student.study("Java");
String str = student.smoke("中华");
System.out.println(str);
System.out.println("-----------------------------------") ;
//方式2:有参构造方法+getXXX()
// public Student(String name, int age, String gender, String hobby, int high) {
Student s2 = new Student("张亮",20,"男","健身",185) ;//必须要符合构造方法的参数的先后顺序
System.out.println("这个学生的信息如下:");
System.out.println("姓名\t年龄\t性别\t身高\t爱好");
System.out.println(s2.getName()+"\t"+s2.getAge()+"\t\t"
+s2.getGender()+"\t\t"+s2.getHigh()+"\t\t"+s2.getHobby());
//调用其他方法
s2.study("Java");
String result = s2.smoke("云烟");
System.out.println(result);
}
}
面试题:创建对象时内存中的过程
题目:有一个学生类,创建学生类对象 Student s = new Student(); 完成了哪些事情?
解析:在面试时不要言简意赅的说“创建了一个对象”,而是应该从你擅长的角度娓娓道来。面试官看的不仅是你的技术能力,而且还看你的表达能力。
解答:
创建对象的整个过程:
- 加载学生类Student.class字节码文件
- 在栈内存中开辟空间
- 在内存中new Student(); 申请堆内存空间
- 申请的同时给当前类中的成员变量进行系统默认的初始化(无参构造方法)
- 在堆内存中产生一个堆内存空间地址值
- 将栈内存空间地址值赋值给栈内存中的变量s
- 栈内存中的变量s指向堆内存地址(类似C语言的指针)
- 当对象创建完毕,GC垃圾回收器在空闲时会回收这些没有更多引用的对象
补充:
在内存中,每一个类都有自己的“签名信息”(版本ID,唯一标识符)。这类似于每个文件的属性中都有文件类型、大小等信息,我们把这些信息叫做元数据。
static关键字
从一个例子说起
/**
* 需求:定义一个Person类,里面有姓名、年龄和国籍的属性
* 提供一个成员方法show方法,显示当前人的成员信息 (为了方便,暂时先不加入私有修饰属性)
* 创建PersonTest类并分别创建一些人,进行测试,
*
*/
class Person{
String name;
int age;
String country;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, String country) {
this.name = name;
this.age = age;
this.country = country;
}
public void show(){
System.out.println("当前人的姓名是"+name+",年龄是:"+age+",所在的国籍是:"+country);
}
}
class PersonTest {
public static void main(String[] args) {
Person p1 = new Person("西施",18,"中国");
p1.show() ;
System.out.println("------------------------------------");
Person p2 = new Person("杨贵妃",28,"中国");
p2.show();
System.out.println("------------------------------------");
Person p3 = new Person("王昭君",25,"中国");
p3.show();
System.out.println("------------------------------------");
Person p4 = new Person("貂蝉",20,"中国");
p4.show();
System.out.println("------------------------------------");
}
}
运行程序的结果如下:
当前人的姓名是西施,年龄是:18,所在的国籍是:中国
------------------------------------
当前人的姓名是杨贵妃,年龄是:28,所在的国籍是:中国
------------------------------------
当前人的姓名是王昭君,年龄是:25,所在的国籍是:中国
------------------------------------
当前人的姓名是貂蝉,年龄是:20,所在的国籍是:中国
------------------------------------
在当前的例子中,四个人的数据都是中国,这样就意味着,每次都要在堆内存中开辟空间并赋值同样的数据,就会浪费了空间。如果想办法让"country"只赋值一次,其他三个人共享这个值,就会节省空间。Java中有一个关键字"static"刚好可以满足需求。
static是静态修饰符,可以被多个对象”共用、共享“。被static修饰的变量就变成了特殊的 静态变量。所以上面的例子有了如下的改进:
class Person{
String name;
int age;
static String country;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, String country) {
this.name = name;
this.age = age;
Person.country = country;
}
public void show(){
System.out.println("当前人的姓名是"+name+",年龄是:"+age+",所在的国籍是:"+country);
}
}
class PersonTest {
public static void main(String[] args) {
Person p1 = new Person("西施",18,"中国");
p1.show() ;
System.out.println("------------------------------------");
Person p2 = new Person("杨贵妃",28);
p2.show();
System.out.println("------------------------------------");
Person p3 = new Person("王昭君",25);
p3.show();
System.out.println("------------------------------------");
Person p4 = new Person("貂蝉",20);
p4.show();
System.out.println("------------------------------------");
}
}
这样会和改进之前的程序输出一样的结果。需要注意的一点是,在改进后的有country变量的构造方法就不要用类的实例访问静态成员变量了,而建议通过类"Person"直接访问静态成员变量。
为了弄清static的加载方式和总结特点,我们先对static关键字进行画图解析。
static关键字的内存图解
用上面的改进后的代码进行内存的图解:
上面的图解说明了:
-
静态相关的东西跟对象无关,与类有关。
-
类只加载一次
static关键字的特点
-
被static修饰的变量或者方法被称为“类变量或类方法”,因为他们是随着类的加载而加载,优先于对象存在的。
-
static不能和this共存
this代表的是当前类对象的地址值引用,创建了对象才能访问;this.变量名表示访问了当前类非静态的成员变量;this.方法名()表示访问当前类非静态的成员方法。
static变量与类一起加载,优先进入内存。二者的生命周期不同,所以
static与this不能共同使用。
-
static可以被多个对象共享共用,这是static最基本的特点。
static关键字的注意事项
-
被静态修饰的变量与方法,一般推荐使用“类名.变量名”或者是“类名.方法名()”访问。不建议通过“对象名.变量名”或者“对象名.方法名”访问。
-
非静态的成员方法,既可以访问静态的变量或方法,也可以访问非静态的变量或方法。而静态的成员方法,只能访问静态的变量或方法。
第2点可以总结为一句话:静态只能访问静态。
static关键字的例子之main方法
开始写第一个Java程序时,就遇到了public static void main(String[] args)
这一串神秘的英文,但是现在我们可以大致解析了。
public static void main(String[] args){}
解析:
public-->权限修饰符,被JVM调用,访问权限是最大的。
static-->静态修饰符,被JVM调用,不用创建对象,直接用类名去访问
void-->没有返回值的返回类型,被JVM调用,不需要给JVM返回值
main-->主方法的通用名称,虽然不是关键字,但是会被JVM识别
String[] args-->用于接收键盘录入的数据
面试题:静态变量和成员变量的区别
-
所属不同
静态变量:属于类,所以也叫做类变量
成员变量:属于对象,所以也叫做实例变量(对象变量)
-
内存中的位置不同
静态变量:存储在方法区里的静态区
成员变量:存储在堆内存
-
内存出现时间不同
静态变量:随着类的加载而加载,随着类的消失而消失
成员变量:随着对象的创建而存在,随着对象的消失而消失
-
调用不同
静态变量:可以通过类名调用,也可以通过对象调用,但是建议通过类名调用
成员变量:只能通过对象调用
帮助文档的制作(了解)
自定义工具类
以后在学习工具类时,会遇到很多静态方法,它们都是拿工具类名去访问。与此同时,我们自己其实也可以定义工具类,方便我们使用。
还没有学习类的时候,我们定义方法都是在定义静态的方法,因为定义的方法要在main方法中调用。如果在类中定义了一个非静态的方法,我们可以创建类对象,访问本类的成员方法。
但是,一般测试类时都是测试其它类,很少见到创建自己本类对象去访问。所以应该将本类定义的一些功能(非静态方法)单独定义到另一个类中。而这另一个类由于集成了一些功能,具有了工具的特性,所以叫做工具类。
工具类的特点:为了保证安全性,使外部类不能创建对象访问,工具类提供了构造方法,将无参构造私有化,而且还有文档注释。
帮助文档制作的方法
制作帮助文档首先要在类前和方法前添加文档注释,填写注释内容。有一些关键字可以帮助生成文档,这里拿几个说明一下。
文档注释中的部分关键字说明
在类前的文档注释:
/**
* @author Chen
* @date 2022/1/15 22:30
* @version V 1.0
* xxx
*/
public class ArrayTool{}
-
@author
:作者 -
@date
:创建java文件的时间 -
@version
:版本号 -
xxx:类的说明
在方法前的文档注释:
/**
* xxx
* @param arr 要查询的指定的数组
* @param target 要查询的指定的数组元素
* @return 如果查找到了,返回的就是当前元素的下标,否则返回-1,找不到
*/
public static int getIndex(int[] arr,int target){}
-
xxx:方法的说明
-
@param xxx
:方法参数的说明 -
@return
:对方法返回值的说明
当然了,在IntelliJ IDEA中我们可以设置模板,自动生成文档注释,具体的操作可以自行百度。
然后对定义的工具类进行解析,解析步骤如下:
进入DOS窗口--->进入当前工具类所在的目录结构--->去掉包名--->保证当前解析的工具类是一个公共访问的(public类型)--->输入命令
javadoc -d 目录名称 -author -version ArrayTool.java(java源文件)
--->以网页的形式自动生成jdk8文档说明书
代码块
在Java中使用{}
包裹起来的就是代码块,根据其位置和声明的不同,可以分为局部代码块、构造代码块、静态代码块、同步代码块。
同步代码块在学习多线程时介绍
-
局部代码块:在方法定义中的
{}
。作用:限定变量的生命周期,使变量及早释放,提高内存的利用率。
-
构造代码块:在类的成员变量(类中方法外)出现的
{}
。作用:对成员的数据进行初始化
特点:如果一个类中有构造代码块,先执行构造代码块,再执行构造方法。(构造代码块的优先级优于构造方法)
开发中很少用到构造代码块,笔试题里出现较多
-
静态代码块:在类中方法外出现,加了static修饰
作用:用于给类进行初始化,在类加载时就执行,并且只执行一次。
代码块的练习
/**
* 看程序写结果
*/
//定义一个类Code
class Code{
//静态代码块
static{
int x =1000 ;
System.out.println(1000);
}
//int num ;
//定义一个无参构造方法
public Code(){
System.out.println("code...");
}
//类中,方法外--成员位置{} 构造代码块
{
int x = 100 ;
System.out.println(x);
// num = 20 ;//可以给成员变量赋值,实际开发中用的构造方法!
}
public Code(int num){
System.out.println("code"+num);
}
//有参构造
public Code(String name){
System.out.println("code..."+name);
}
//构造代码块
{
int y = 200 ;
System.out.println(y);
}
//静态代码块
static{
int y = 2000 ;
System.out.println(y);
}
}
public class CodeDemo {
public static void main(String[] args) {
//局部代码块
{
int x = 10;
System.out.println(x);
int y = 20;
System.out.println(y);
}
System.out.println("-------------------------------") ;
//通过无参构造方法new对象
Code code = new Code() ;
System.out.println("-------------------------------") ;
Code code2 = new Code("hello") ;
}
}
答案:
10
20
-------------------------------
1000
2000
100
200
code...
-------------------------------
100
200
code...hello
执行规律总结:静态代码块>构造代码块>构造方法
继承
继承的概念
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,让这个类和多个类产生一种关系,这种关系就称为继承。有了继承关系的存在,多个类无需再定义这些属性和行为,只要继承那个类即可。在Java中,实现类与类的继承可以通过extends关键字。
继承的格式
class 父类名{}
class 子类名 extends 父类名{}
继承的案例
//父类
class Person {
private String name;
private int 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 void eat(){
System.out.println("饿了就需要吃饭");
}
public void sleep(){
System.out.println("困了就需要睡觉");
}
}
//子类学生类
class Student extends Person{
@Override
public void eat() {
System.out.println("学生需要吃饭");
}
@Override
public void sleep() {
System.out.println("学生需要休息");
}
}
//子类程序员类
class Programmer extends Person{
@Override
public void eat() {
System.out.println("程序员需要吃饭");
}
@Override
public void sleep() {
System.out.println("程序员需要休息");
}
}
//测试类
class TestPerson{
public static void main(String[] args) {
Programmer p = new Programmer();
p.setName("爱学cs的小陈");
p.setAge(18);
System.out.println(p.getName()+"---"+p.getAge());
p.eat();
p.sleep();
System.out.println("--------------------------------------");
Student s = new Student();
s.setName("爱学ee的小梦");
s.setAge(20);
System.out.println(s.getName()+"---"+s.getAge());
s.eat();
s.sleep();
}
}
运行结果:
爱学cs的小陈---18
程序员需要吃饭
程序员需要休息
--------------------------------------
爱学ee的小梦---20
学生需要吃饭
学生需要休息
继承的好处
-
提高了代码的复用性
-
提高了代码的维护性
-
让类与类产生了关系,这是多态的前提
这也是继承的一个弊端:类的耦合性很强
设计原则:高内聚低耦合
简单理解:内聚就是自己完成某件事情的能力,耦合就是类与类之间的关系。所以可以这么解释:自己能干的就不要影响别人,这样别人将来改了代码,对我的影响也会最小。
博客难免会产生一些错误。如果写的有什么问题,欢迎大家批评指正。