JavaSE之面向对象(中)

一、封装

1、封装概述:

面向对象编程语言是对客观世界的描述,客观世界里的成员变量都是隐藏在对象内部,外界无法直接进行操作。封装可以认为是一个保护罩,可以防止该类中的成员变量和方法被其他类随意访问和修改。要访问该类的数据,必须通过指定的方法。

2、高内聚低耦合

高内聚:类内数据操作细节自己完成,不允许类外干涉
低耦合:仅对外部提供少量方法
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

3、类的封装
把属性和方法封装在类内,外部通过调用方法完成对类内指定属性或方法进行操做,不必知道类内具体实现。

二、属性的封装

1、原则
将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。

2、属性封装的目的
1.隐藏类的实现细节
2.使使用者只能通过指定方法访问,限制危险访问
3.可以进行数据检查,从而有利于保证对象信息的完整性
4.便于修改,提高代码的可维护性

3、实现步骤

1.使用private修饰成员变量
2.使用get/set方法访问成员变量

三、子类继承父类问题

class Father {
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    public void sayHi() {
        System.out.println("My name is " + name);
    }
}

class Son extends Father {}

public class PrivateFieldTest {
    public static void main(String[] args) {
        Father f1 = new Father();
        Son s1 = new Son();
        f1.sayHi();
        s1.sayHi();
        System.out.println();
        f1.setName("Sam");
        f1.sayHi();
        s1.sayHi();
        System.out.println();
        s1.setName("Tom");
        f1.sayHi();
        s1.sayHi();
    }
}
  • 对于子类继承问题,官方解释为子类不继承父类中private的成员。但是父类中如果有公用的方法可以访问private成员的话,子类是可以访问父类的private成员。
  • 嵌套类可以访问其封闭类的所有private成员,包括字段和方法。因此,子类继承的公共或受保护的嵌套类可以间接访问超类的所有private成员。
  • 另外一种解释认为,子类将父类的全部成员和方法继承。父类的私有属性和方法子类无法访问。而子类可以通过调用父类中的public修饰的get/set方法访问私有成员。父类的private修饰的私有成员会继承到子类中,子类可以拥有但不能去使用。
  • 内存中表现为,当一个子类被实例化时,默认先调用父类的构造方法对父类进行初始化,即在内存中创建一个父类对象,然后在父类对象外面创建子类独有的属性,这才是一个完整的子类对象。
  • 再次,我们总结一下,子类不能继承父类的私有成员,但子类的对象包括自己独有的成员、继承来的成员以及父类的私有成员。
  • 通过上述的程序可以看出,虽然子类不能继承父类的私有成员,但还是可以通过公有方法访问私有成员。另外值得注意的是:如果父类有无参构造方法,那么子类的构造方法中可以不使用super调用。

无参构造

class Father {
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getName(){
		return name;
	}
	
    public void sayHi() {
        System.out.println("My name is " + name);
    }
}

class Son extends Father {}

public class PrivateFieldTest {
    public static void main(String[] args) {
    	//创建子类对象
        Son s = new Son();
        //用set方法赋值
        s.setName("张三");
        System.out.println(s.getName);
    }
}
创建子类对象,调用空参构造,用set方法赋值,用get方法获值是完全没有问题的,因为子类继承父类的时候,若子类中没有任何成员时,子类只能通过无参构造对成员变量进行初始化,然后通过set方法赋值.

有参构造

class Son{
		public Son(){
			//为了可以使用无参,我们把无参也写上
		}

		public Son(String name,int age){
			this.setName(name);
    		this.setAge(age);
			// 也可以用super.setName  和 super.setAge 
			// 也可以用 super(name, age) 调用父类有参构造对父类成员进行初始化(建议)
		}
}
public class test{
	public static void main(String[] args){
		//创建子类对象
		Son s = new Son("张三",20); 
		System.out.println(s.getName+","+s.getAge);
	}
}
如果父类中只有参构造而没有无参构造时,在子类中必须对父类的有参构造进行显式调用,因为子类成员初始化之前会对父类成员进行初始化. 也就是说,若子类构造第一行代码没有调用父类构造,也没有调用子类构造,则默认调用父类无参构造,但父类中若没有无参构造,那只能在子类中显式调用出来,不然,父类成员变量无法进行初始化,子类无法使用.

原文链接:https://blog.csdn.net/qq_20085465/article/details/78439543

四、构造器

注意:

1.构造名必须与类名一致。
2.没有返回值,因此不需要返回值类型,也不需要void
3.如果没有定义构造器的话,会自动生成无参构造器,修饰符默认与类的修饰符一致
4.如果定义了构造器,则不会再提供无参构造器
5.构造器可以重载,包括无参与有参
6.构造器不能被static、final、synchronized、abstract、native修饰

五、this关键字

1、this代表当前对象的引用

1.this用于构造器中,表示正在创建的那个实例对象,正在new谁,谁就是this;比如Son s = new Son("张三",20); 此时this代表的就是s。
2.this用于实例方法中,表示调用方法的那个对象,谁在调用,谁就是this

2、this使用格式

1.当方法的局部变量与当前对象的成员变量重名时。可以在成员变量前加this.;认为用于区分成员变量与局部变量。即重名问题。[this.成员变量名]
2.调用当前对象自己的成员方法时,都可以加"this.",也可以省略,实际开发中都省略。[this.成员方法]
3.当需要调用本类的其他构造器时,就可以使用该形式。[this()或this(实参列表)]

需要注意的是

1.this不能用在static方法中!
2.如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(【实参列表】)",否则会发生递归调用死循环

我们可以看一下代码解释:

public class ThisTest {

  	 private int i=0;

    //第一个构造器:有一个int型形参

    ThisTest(int i){

       this.i=i+1;//此时this表示引用成员变量i,而非函数参数i

       System.out.println("Int constructor i——this.i:  "+i+"——"+this.i);

       System.out.println("i-1:"+(i-1)+"this.i+1:"+(this.i+1));

       //从两个输出结果充分证明了i和this.i是不一样的!

    }

    //  第二个构造器:有一个String型形参

    ThisTest(String s){

       System.out.println("String constructor:  "+s);

    }

    //  第三个构造器:有一个int型形参和一个String型形参

    ThisTest(int i,String s){

       this(s);//this调用第二个构造器;此类型属于第三种情况

       //this(i);

       /*此处不能用,因为其他任何方法都不能调用构造器,只有构造方法能调用他。

       但是必须注意:就算是构造方法调用构造器,也必须为于其第一行,构造方法也只能调

       用一个且仅一次构造器!*/

       this.i=i++;//this以引用该类的成员变量

       System.out.println("Int constructor:  "+i+"/n"+"String constructor:  "+s);

    }
此代码与部分解释来自于:原文链接:https://blog.csdn.net/fzfengzhi/article/details/2174406

在此,我们谈一下为何要使用构造器,同样用代码进行解释:

public class Student{
	private String name;
	private int age;
	
	public Student() {};
	
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public int getAge() {
		return age;
	}
}

以上代码中的无参构造和有参构造部分主要是为了创建对象和对象的初始化。

构造方法的作用:
1.为了初始化成员属性,而不是初始化对象,初始化对象是通过new关键字实现的
2.通过new调用构造方法初始化对象,编译时根据参数签名来检查构造函数,称为静态联编和编译多态(参数签名:参数的类型,参数个数和参数顺序)
3.创建子类对象会调用父类构造方法但不会创建父类对象,只是调用父类构造方法初始化父类成员属性

构造器与方法的总结:

方法实际上是需要用于执行java代码的
构造器实际上是一个类的实例,这是因为在初始化一个类时,java需要开辟一个新的内存空间,而java又是根据什么?所以我们需要为这个类创建一个构造器,而这个构造器的名称要和类名一致,所以java才能识别这个类,进而为这个类开辟内存空间。总之,我们在手动的为类“初始化”。

原文链接:https://blog.csdn.net/qiuzhongweiwei/article/details/78965788

六、包(Package)

1、包的作用:

1.可以避免重名,比如:包.类名
2.分类组织管理众多的类:java.lang、java.net、java.util、java.io 、java.text、java.sql和javax.sql、java.awt和java.swing
3.可以控制某些类型或成员的可见范围:如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用

2、包的命名格式

1.package 包名;
2.包名:com.atguigu.xxx;
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置

七、static关键字

stactic是一个成员修饰符,可以修饰的成员包括成员变量、成员方法、成员内部类、代码快。而被修饰的成员是属于类的,属于类就可以不通过创建对象来调用。

1、静态方法

[修饰符] static 返回值类型 方法名([形参列表]) {}

注意:

1.本类中,静态方法可以直接访问静态变量和静态方法
2.在其他类中,可以使用“类名.方法"进行调用,也可以使用"对象名.方法",推荐使用“类名.方法"
3.在静态方法中,不能出现this!不能直接使用本类的非静态成员。
4.非静态的实例成员方法可以直接访问静态的类变量和方法。

这是因为this与非静态的成员,这些都是需要创建对象时,才能使用的,而静态方法调用时,可能没有对象。

2、静态变量:由static修饰的成员变量

1.该成员变量可以由该类所有对象共享
2.类变量的值和类信息一起存放在方法区中
3.在static方法中如果有局部变量与类变量重名时,使用“类名.成员变量"进行区别
4.该成员变量的get/set也是静态的。

我们来看一下有关于调用静态成员的代码和内存模型

class Preson{
    private static String school = "张老大幼儿园";
    private String name;

    public Preson(String name) {
        super();
        this.name = name;
    }
    // 省略掉get/set部分
}

public class Student {
    public static void main(String[] args) {
        Preson p1 = new Preson("张三");
        Preson p2 = new Preson("李四");

        p1.setSchool("王老大幼儿园");

        Preson.setSchool("wangSchool");
    }
}

八、变量的分类与区别

1、按照数据类型:

1.基本数据类型,存放的是数据值
2.引用数据类型,存放的是对象的地址值

2、按照声明的位置:

1.成员变量:类内,方法外
2.局部变量:类内,方法内

3、成员变量与局部变量的区别

1.按照声明位置
2.存储的位置:

(1)成员变量:
静态成员变量在方法区中的常量池中
非静态的成员变量(实例变量),在堆中
(2)局部变量在栈中

3.修饰符不同

(1)成员变量有四种权限修饰符等其他修饰符,static修饰静态
(2)局部变量不使用修饰符

4.作用域不同

(1)静态变量作用于整个类,在其他类中通过“类名.静态变量”使用
(2)非静态变量只能在非静态的成员中使用,在其他类中通过“对象名.变量名”使用
(3)局部变量,作用于本方法中

5.生命周期不同

(1)静态变量:随着类的加载而分配,类的卸载而消亡
(2)非静态变量:随着对象的创建才会在堆中分配内存,对象被垃圾回收而消亡
(3)局部变量:每次方法调用时创建,方法结束时被回收。

在一些java的工具类里,封装了一些静态方法,这样做可以直接为静态方法分配了一个固定的内存空间,可以被类名调用,不需要创建对象。可以直接传入静态变量访问静态方法。

九、继承

1. 叙述:

继承描述的是事物的所属关系(is-a)。继承就是子类继承父类的属性和方法,子类对象拥有与父类相同的属性和行为,同时又拥有自己专属的属性和方法。

2. 继承的好处

提高代码的复用性,扩展性。在类和类之间创建关系,是学习多态的前提。

3. 继承的特点

1.私有化
父类的对象,无论是public公共的还是private私有的都会被子类继承。但不同的是,对于私有成员,子类只是拥有却无法直接使用,可以通过继承父类的公共方法进行访问。

2.成员变量重名情况
如果子类中存在与父类重名的成员变量时,莽撞的访问会带来问题。

我们来看一下下面代码

class Animal {

    public String name;
    public int age;

    int num = 3;
    /*public void eat() {
        System.out.println(name + ' ' + age + ' ' + "吃饭");
    }*/
}

class Cat extends Animal {
    int num = 4;

    public void move() {
        System.out.println(num);
    }
}

public class AnimalText {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name = "Tom";
        cat.age = 2;
        //cat.eat();
        cat.move();
    }
}
我们在子类中调用和父类相同名字的成员变量时可以发现,父类的成员变量被子类修饰掉了。因此想要分别调用父类和子类的成员变量需要进行以下修改
class Animal {

    public String name;
    public int age;

    int num = 3;
    /*public void eat() {
        System.out.println(name + ' ' + age + ' ' + "吃饭");
    }*/
}

class Cat extends Animal {
    int num = 4;

    public void move() {
        System.out.println(this.num);
        System.out.println(super.num);
    }
}

public class AnimalText {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name = "Tom";
        cat.age = 2;
        //cat.eat();
        cat.move();
    }
}

子类中调用本类的成员变量使用this.关键字;子类中调用父类成员变量使用super.关键字。这么使用的前提是子类和父类的成员变量名相同。

> 3.成员方法重名情况
当子类的成员方法与父类的成员方法重名时,这个时候就是一种特殊的情况,方法重写。
class Animal {

    public String name;
    public int age;

    int num = 3;
    public void eat() {
        System.out.println(name + ' ' + age + ' ' + "吃饭");
    }
}

class Cat extends Animal {
    int num = 4;

    /*public void move() {
        System.out.println(this.num);
        System.out.println(super.num);
    }*/

    public void eat() {
        System.out.println("eat方法重写了!");
    }
}

public class AnimalText {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name = "Tom";
        cat.age = 2;
        cat.eat();
        //cat.move();
    }
}

我们发现子类的方法实际上覆盖了父类的方法。
在父子类的继承关系中,创建子类对象,调用成员方法,创建的对象是谁的就优先用谁,找不到向上查找(即父类),不会再向下一级查找。
如果相对父类的方法进行更新,可以采用覆盖重写。

注意事项:

1.必须保证方法名与参数列表一致,在方法前使用@Override注解检测覆盖重写是否正确,但却不是必须的。
2.子类方法的返回值类型必须【小于等于】父类方法的返回值类型
3.子类方法的权限必须【大于等于】父类方法的权限修饰符。(public > protected > 缺省 > private);缺省是什么都不写,留空。
4.静态方法不能被重写;私有等在子类中不可见的方法不能被重写;final方法不能被重写

我觉得这里需要说明一下重写与重载的区别:

1.重写:方法名一样,参数列表一样,且是父子类关系
2.重载:方法名一样,参数列表不一样,在同一个类内

构造方法

1.由于构造方法名与类名一致,故子类无法继承父类的构造方法
2.但是在子类的初始化过程中,会先执行父类的初始化。子类的构造方法中会有一个super();表示调用了父类的构造方法。
3.如果父类没有无参构造,同样在子类的构造方法中用super(...,...)调用父类的构造器。

十、final

1. 概述

最终的,不可更改的
final类:太监类,没有子类
final方法:表示这个方法不能被子类重写
final变量:表示此变量值不可更改,即常量

2. final变量

final可以修饰成员变量和局部变量;
修饰成员变量时,没有set方法,并且必须有显示赋值语句,不能使用成员变量默认值;
final修饰的变量名,所有字母都大写

3. final修饰符、finally修饰符与finallize()

在异常处理时提供finally块来执行任何清除操作。如果抛出一个异常,那么相匹配的catch字句就会执行,然后控制就会进入finally块中。

finalize是方法名。Java技术允许使用finallize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法使用垃圾收集器在确定这个对象没有被引用时对这个对象调用的。总之,finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

十一、类初始化

1. 静态变量在类中的初始化

类被加载内存后,会在方法区创建一个class对象来存储该类的信息。此时会在方法区为静态变量开辟内存,然后为类变量进行初始化。实际上,类初始化的过程时在调用一个()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化()方法体中。

(1)静态成员变量的显示赋值语句
(2)静态代码块中的语句
(3)静态代码块对静态变量的二次赋值会覆盖之前的静态变量声明的初始值。

public class Test{
    public static void main(String[] args){
    	Father.test();
    }
}
class Father{
	private static int a = getNumber();
	static{
		System.out.println("Father(1)");
	}
	private static int b = getNumber();
	static{
		System.out.println("Father(2)");
	}
	
	public static int getNumber(){
		System.out.println("getNumber()");
		return 1;
	}
	
	public static void test(){
		System.out.println("Father:test()");
	}
}
运行结果:
getNumber()
Father(1)
getNumber()
Father(2)
Father:test()

附图:单个类的静态成员变量的初始化内存模型

原文链接:https://blog.csdn.net/weixin_41043145/article/details/95868669

public class Test{
    public static void main(String[] args){
    	Son.test();
        System.out.println("-----------------------------");
        Son.test();
    }
}
class Father{
	private static int a = getNumber();
	static{
		System.out.println("Father(1)");
	}
	private static int b = getNumber();
	static{
		System.out.println("Father(2)");
	}
	
	public static int getNumber(){
		System.out.println("Father:getNumber()");
		return 1;
	}
}
class Son extends Father{
	private static int a = getNumber();
	static{
		System.out.println("Son(1)");
	}
	private static int b = getNumber();
	static{
		System.out.println("Son(2)");
	}
	
	public static int getNumber(){
		System.out.println("Son:getNumber()");
		return 1;
	}
	
	public static void test(){
		System.out.println("Son:test()");
	}	
}
运行结果:
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son:test()
-----------------------------
Son:test()

2. 实例初始化

实例初始化方法的方法体,由四部分构成:
(1)super()或super(实参列表) 这里选择哪个,看原来构造器首行是哪句,如果没写super()或super(实参列表),默认就是super()
(2)非静态实例变量的显示赋值语句
(3)非静态代码块
(4)对应构造器中的代码

实例化的特点:
(1)只有创建对象时,才会执行对应的初始化
(2)调用哪个构造器,就是指定它对应的实例初始化方法
(3)创建子类对象时,父类对应的实例初始化会被先执行,执行父类哪个实例初始化方法,看用super()还是super(实参列表)

public class Test{
    public static void main(String[] args){
    	Son s1 = new Son();
    	System.out.println("----------------------------");
    	Son s2 = new Son();
    }
}
class Father{
	static{
		System.out.println("Father:static");
	}
	{
		System.out.println("Father:not_static");
	}
	Father(){
		System.out.println("Father()无参构造");
	}
}
class Son extends Father{
	static{
		System.out.println("Son:static");
	}
	{
		System.out.println("Son:not_static");
	}
	Son(){
		System.out.println("Son()无参构造");
	}
}

运行结果:
Father:static
Son:static
Father:not_static
Father()无参构造
Son:not_static
Son()无参构造
----------------------------
Father:not_static
Father()无参构造
Son:not_static
Son()无参构造

3. 结论(类初始化与实例初始化)

(1)类初始化先于实例初始化
(2)类初始化只执行一次,在第一次被加载时
(3)实例初始化每次创建对象时都要执行

4. 构造器和非静态代码块

  从某种程度上,我们可以将非静态代码块看作是对构造器的补充,非静态代码块总是在构造器之前执行。与构造器不同的是,非静态代码块是一段固定的代码,不能接受任何数据。因此非静态代码块对同一个类的所有对象所进行的初始化处理完全相同。如果有一段初始化处理代码对所有对象完全相同,且无须接收任何参数,就可以把这段初始化处理代码提取到非静态代码块中。
  因此,非静态代码块中存放的是一段初始化处理代码对所有对象完全相同,且无需接受任何参数。通过把多个构造器中相同代码提取到非静态代码块中定义,能更好地提高初始代码的复用,提高整个应用的可维护性。

this/super问题

(1)当方法中访问变量时,如果没有this.,super.,那么都是遵循就近原则,找最近声明的。

public class Test{
    public static void main(String[] args){
    	Son s = new Son();
    	System.out.println(s.getNum());//10
    	
    	Daughter d = new Daughter();
    	System.out.println(d.getNum());//20
    }
}
class Father{
	protected int num = 10;
	public int getNum(){
		return num;
	}
}
class Son extends Father{
	private int num = 20;
}
class Daughter extends Father{
	private int num = 20;
	public int getNum(){
		return num;
	}
}

(2)找方法时

没有加this.和super.的,默认就是加了this.的。
如果加了this.,先从当前对象的类中找,如果没有自动从父类中查找。
如果加了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
		System.out.println(this.getNum());//10
		System.out.println(super.getNum());//10
	}
}
class Daughter extends Father{
	private int num = 20;
	public int getNum(){
		return num;
	}
	public void test(){
		System.out.println(getNum());//20
		System.out.println(this.getNum());//20
		System.out.println(super.getNum());//10
	}
}
posted @ 2020-07-04 13:22  梧桐更兼细雨_到黄昏  Views(174)  Comments(0Edit  收藏  举报