面向对象

SOLID https://blog.csdn.net/e5Max/article/details/8872182 https://zhuanlan.zhihu.com/p/44344256

特点

  1. 面向对象就是一种常见的思想,符合人们思考习惯
  2. 面向对象的出现,将复杂问题简单化
  3. 面向对象的出现,将执行者变为指挥者
  • 对象说白了也是一种数据结构(对数据的管理模式),将数据和数据的行为放到了一起。
  • 在内存上,对象就是一个内存块,存放了相关的数据集合!
  • 对象的本质就一种数据的组织方式!

类和对象

类可以看做是一个模版,或者图纸,系统根据类的定义来造出对象。我们要造一个汽车,怎么样造?类就是这个图纸,规定了汽车的详细信息,然后根据图纸将汽车造出来。

类:我们叫做class。 对象:我们叫做Object,instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思

  1. 对象是具体的事物;类是对对象的抽象;
  2. 类可以看成一类对象的模板,对象可以看成该类的一个具体实例。
  3. 类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性、方法。

用一段代码来解释所有基础概念,如下:

// 类
public class Human {
    private static int number=0; //类变量
    private  final String name;// final必须初始化或者构造函数初始化
    private   int weight; //private修饰符只能在本类中访问
    
    // 构造函数1
    public Human(){
        this.name="default";
        this.weight=100;
        number += 1;
    }
    // 构造函数2
    public  Human(String name,int weight){
        this.name=name;
        this.weight=weight;
    }
    
    // 设置name的值
    public void setName(String name){
        name = name;
    }
    //获取name的值
    public  String getName(){
        return  this.name;
    }
    
    //设置weight的值
    public void setWeight(int weight){
        weight = weight;
    }
    //获取weight的值
    public  int getWeight(){
        return this.weight;
    }
    
    //获取number的值
    public static int getNumber(){
        return Human.number;
    }
    //设置number的值
    public static void setNumber(int number) {
        Human.number = number;
    }
    
    //方法 改变weight的值
    public int workout(){
        weight = weight-5;
        return  weight;
    }
    
    //静态方法 调用构造函数初始化值
    public static Human newHuman(String name,int weight){
        return  new Human(name,weight);
    }
}

//对象
Human jeff = new Human("jeff",120); //实例化对象并初始化值
System.out.println(jeff.getName()); //jeff 获取name
System.out.println(jeff.getWeight());//120 获取weight
jeff.setName("David");//设置name
Human david = jeff;//对象地址传递

david.workout();
System.out.println(jeff.getWeight());//115
System.out.println(david.getWeight());//115

System.out.println(Human.getNumber());//0
System.out.println(Human.getNumber());//0

Human kitty = Human.newHuman("kitty",120);
System.out.println(kitty.getName());//kitty

对象和类内存图解

内存

Java虚拟机的内存可以分为三个区域:栈stack、堆heap、方法区method area。

栈的特点如下

  1. 栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
  2. JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
  3. 栈属于线程私有,不能实现线程间的共享!
  4. 栈的存储特性是“先进后出,后进先出”
  5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!

堆的特点如下

  1. 堆用于存储创建好的对象和数组(数组也是对象)
  2. JVM只有一个堆,被所有线程共享
  3. 堆是一个不连续的内存空间,分配灵活,速度慢!

方法区(又叫静态区)特点如下

  1. JVM只有一个方法区,被所有线程共享!
  2. 方法区实际也是堆,只是用于存储类、常量相关的信息!
  3. 用来存放程序中永远是不变或唯一的内容。(类信息【Class对象】、静态变量、字符串常量等)

代码如下

public class SxtStu {
	int id;
	String name;
	int age;
	Computer computer;
	void study() {
		System.out.println("正在学习" + computer.brand);
	}
	void play() {
		System.out.println("王者农药");
	}
}
class Computer{
	String brand;
}
//main
public static void main(String[] args) {
    // TODO Auto-generated method stub
    SxtStu stu = new SxtStu();
    stu.age=12;
    stu.name="zhangshang";
    stu.id=1;
    Computer computer = new Computer();
    computer.brand = "thinkpad";
    stu.computer = computer;
    stu.study();
}

内存管理

垃圾回收

内存管理 Java的内存管理很大程度指的就是对象的管理,其中包括对象空间的分配和释放。

对象空间的分配:使用new关键字创建对象即可

对象空间的释放:将对象赋值null即可。垃圾回收器将负责回收所有”不可达”对象的内存空间。

垃圾回收过程 任何一种垃圾回收算法一般要做两件基本事情:

  1. 发现无用的对象

  2. 回收无用对象占用的内存空间。

垃圾回收机制保证可以将“无用的对象”进行回收。无用的对象指的就是没有任何变量引用该对象。Java的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。

垃圾回收相关算法

  1. 引用计数法 堆中每个对象都有一个引用计数。被引用一次,计数加1. 被引用变量值变为null,则计数减1,直到计数为0,则表示变成无用对象。优点是算法简单,缺点是“循环引用的无用对象”无法别识别
    public class Student {
    String name;
    Student friend;
    
        public static void main(String[] args) {
            Student s1 = new Student();
            Student s2 = new Student();
    
            s1.friend = s2;
            s2.friend = s1;        
            s1 = null;
            s2 = null;
        }
    }
    
  2. 引用可达法(根搜索算法) 程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

通用的分代垃圾回收机

分代垃圾回收机制,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状态:年轻代、年老代、持久代。JVM将堆内存划分为 Eden、Survivor 和 Tenured/Old 空间。

  1. 年轻代 所有新生成的对象首先都是放在Eden区。 年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是Minor GC,每次 Minor GC 会清理年轻代的内存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当“年轻代”区域存放满对象后,就将对象存放到年老代区域。

  2. 年老代 在年轻代中经历了N(默认15)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多,我们就需要启动Major GC和Full GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域。

  3. 持久代 用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响。

·Minor GC: 用于清理年轻代区域。Eden区满了就会触发一次Minor GC。清理无用对象,将有用对象复制到“Survivor1”、“Survivor2”区中(这两个区,大小空间也相同,同一时刻Survivor1和Survivor2只有一个在用,一个为空) ·Major GC: 用于清理老年代区域。 ·Full GC: 用于清理年轻代、年老代区域。 成本较高,会对系统性能产生影响。

垃圾回收过程 1、新创建的对象,绝大多数都会存储在Eden中,

2、当Eden满了(达到一定比例)不能创建新对象,则触发垃圾回收(GC),将无用对象清理掉,然后剩余对象复制到某个Survivor中,如S1,同时清空Eden区

3、当Eden区再次满了,会将S1中的不能清空的对象存到另外一个Survivor中,如S2, 同时将Eden区中的不能清空的对象,也复制到S1中,保证Eden和S1,均被清空。

4、重复多次(默认15次)Survivor中没有被清理的对象,则会复制到老年代Old(Tenured)区中,

5、当Old区满了,则会触发一个一次完整地垃圾回收(FullGC),之前新生代的垃圾回收称为(minorGC)

JVM调优和Full GC

在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:

1.年老代(Tenured)被写满

2.持久代(Perm)被写满

3.System.gc()被显式调用(程序建议GC启动,不是调用GC)

4.上一次GC之后Heap的各域分配策略动态变化

开发中容易造成内存泄露的操作

建议

在实际开发中,经常会造成系统的崩溃。如下这些操作我们应该注意这些使用场景。 请大家学完相关内容后,回头过来温习下面的内容。不要求此处掌握相关细节。

如下四种情况时最容易造成内存泄露的场景,请大家开发时一定注意:

  • 创建大量无用对象

比如,我们在需要大量拼接字符串时,使用了String而不是StringBuilder。

String str = "";
for (int i = 0; i < 10000; i++) {   
    str += i;     //相当于产生了10000个String对象
}
  • 静态集合类的使用

像HashMap、Vector、List等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放。

  • 各种连接对象(IO流对象、数据库连接对象、网络连接对象)未关闭

IO流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,和硬盘或者网络连接,不使用的时候一定要关闭。

  • 监听器的使用

释放对象时,没有删除相应的监听器。

要点

  1. 程序员无权调用垃圾回收器。

  2. 程序员可以调用System.gc(),该方法只是通知JVM,并不是运行垃圾回收器。尽量少用,会申请启动Full GC,成本高,影响系统性能。

  3. finalize方法,是Java提供给程序员用来释放对象或资源的方法,但是尽量少用。

变量

匿名对象

参数传递

修饰符

访问控制修饰符

  • default 缺省,什么都不写,同一包内可见
  • private 同一类中可见,不能修饰类和接口
  • public 所有类可见,所有方法和变量都能被子类继承
  • protected 同一包内的类和子类可见,不能修饰类和接口及接口的变量方法
public class Human {
    private static int number=0; 
    private  final String name;
    private   int weight; 
    //private修饰符只能在本类中访问,可通过set,get方法赋值取值
  • 父类中声明为public的方法在子类中必须为public
  • 父类中声明为protected的方法在子类中可以为protected,public,不能为private
  • 父类中声明为default的方法在子类中可以为protected,public,不能为private
  • 父类中声明为private的方法,不能被继承

package niliv.haha;
public class TestModifier1 {
	
	private String name;
	String sex;
	protected String age;
	public String major;
	
	public TestModifier1(String name, String sex, String age, String major) {
		super();
		this.name = name;
		this.sex = sex;
		this.age = age;
		this.major = major;
	}
	
	void speak() {
		System.out.println("TestModifier1");
	}
}
//同包中的子类
class TestSon extends TestModifier1{
	
	public TestSon(String name, String sex, String age, String major) {
		super(name, sex, age, major);
		// TODO Auto-generated constructor stub
	}

	protected void speak() {
		System.out.println("TestSon");
	}
}

package niliv.haha;
//同一个包的类
public class TestModifiler2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TestModifier1 t1 = new TestModifier1("旺财", "男", "12", "警犬");
		t1.name=""; //The field TestModifier1.name is not visible
		t1.sex=""; //default
		t1.age =""; //protected
		t1.major=""; //public
	}
}

//不同包中的子类
package niliv.oop;
import niliv.haha.TestModifier1;
public class TestModifier3 extends TestModifier1 {

	public TestModifier3(String name, String sex, String age, String major) {
		super(name, sex, age, major);
		// TODO Auto-generated constructor stub
	}
	
	void say() {
		System.out.println(name); //The field TestModifier1.name is not visible
		System.out.println(sex); //The field TestModifier1.sex is not visible
		System.out.println(age);
		System.out.println(major);
	}

}
  1. 一般使用private访问权限。
  2. 提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作(注意:boolean变量的get方法是is开头!)。
  3. 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。

非访问修饰符

  • static 修饰方法和类变量
  • final 修饰类、方法和变量,类不能被继承,方法不能被继承类重新定义,变量不可修改
  • abstract 创建抽象类和抽象方法

static

public class Human {
    private static int number=0; //类变量 静态变量
    
    //获取number的值 静态方法不能使用类的非静态变量
    public static int getNumber(){
        return number;
    }
    //设置number的值
    public static void setNumber(int number) {
        Human.number = number;
    }
}
//静态方法和变量使用类名来调用
System.out.println(Human.getNumber());//0
System.out.println(Human.getNumber());//0

final private final String name; // final必须初始化或者构造函数初始化

abstract

抽象类不能实例化对象,目的是为了将来对该类进行扩充。
如果一个类包含抽象方法,该类一定要声明为抽象类,抽象类则可以包含抽象方法和非抽象方法。
抽象方法不能被声明为static和final。
继承抽象类的子类必须实现父类的所有抽象方法。

//抽象类
public abstract class fClass {
    abstract  void m();
}
//子类
public class sClass extends fClass {
    @Override
    void m() {
        System.out.println("i am subclass,i extends abstract");
    }
    public  sClass(){
    }
}
//调用
sClass sc = new sClass();
sc.m();

构造函数

创建对象时调用的函数,可以初始化对象,创建对象都必须要通过构造函数初始化

  • 不需要返回值,可以有return
  • 给对象进行初始化
  • 多个构造函数以重载的形式存在
  • 名称必须和类名一致
  1. 类中如果没有定义构造函数,会有一个默认的空参数构造函数
  2. 如果在类中定义了构造函数,那么类中的默认构造函数就没有了

this

创建一个对象分为如下四步:

  1. 分配对象空间,并将对象成员变量初始化为0或空

  2. 执行属性值的显示初始化

  3. 执行构造方法

  4. 返回对象的地址给相关的变量

this的本质就是“创建好的对象的地址”! 由于在构造方法调用前,对象已经创建。因此,在构造方法中也可以使用this代表“当前对象” 。

this就是所在函数所属对象的引用 哪个对象调用了this所在的函数,this就代表哪个对象 this不能用于static方法中

Person(String name){
    this.name = name;
}
//构造函数互相调用,只能放在第一行
Person(int age,String name){
    this(name);
    this.age = age;
}

public class TestThis {
	
	private String name;
	private int age;
	
	public TestThis() {
		this.name = "niliv";
		this.age = 35;
		System.out.println("我是无参构造函数 " + this.name + " " + this.age);
	}
	public TestThis(String name) {
		//this();
		this.name = name;
		System.out.println("我是name构造函数 " + this.name + " " + this.age);
	}
	public TestThis(String name,int age) {
		this.name = name;
		this.age = age;
		System.out.println("我是全参构造函数 " + this.name + " " + this.age);
	}
	public void speak() {
		System.out.println("当前对象:" + this + " name:"+this.name+ " age:"+this.age);
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TestThis tt1 = new TestThis("旺财");
		tt1.speak();
		TestThis tt2 = new TestThis("小强",1200);
		tt2.speak();
		
//		我是无参构造函数 niliv 35
//		我是name构造函数 旺财 35
//		当前对象:TestThis@15db9742 name:旺财 age:35
//		我是全参构造函数 小强 1200
//		当前对象:TestThis@6d06d69c name:小强 age:1200
	}

}

TestThis@15db9742解析 getClass().getName() + '@' + Integer.toHexString(hashCode()) 所属类的全名(包名+类名)+@+此对象哈希码的无符号十六进制表示(可以大致理解为该对象在内存中的地址)

static

private static String countray; Person.countray

特点

  1. 数据共享 所有对象都可以使用这个变量
  2. static优先对象存在,跟着类的加载就加载了
  3. static修饰的成员可以被类和对象调用
  4. static存储的共享数据,对象中存储的是特有数据
  5. 静态方法只能访问静态成员(非静态都可以访问)
  6. 静态方法中不能使用this和super
  7. 主函数是静态的,直接调用的只能是静态方法

成员变量和静态变量区别

  • 生命周期不一样,成员变量随着对象的存在而存在,静态变量随着类的存在而存在(JVM释放,缺点:太长)
  • 调用方式不同
  • 别名不同,成员变量称为实例变量,静态变量称为类变量
  • 存储位置不同,成员变量在堆中,静态变量在方法区的静态区中

main函数 public:权限必须最大 static:不需要初始化对象,jvm直接调用 main:jvm识别的名称 String[] args:参数列表

  1. 为什么main()方法是静态的(static)

a. 我们知道,静态方法的好处之一就是在第三方调用的时候不需要持有该类的对象,直接通过(类名.方法名)的形式就可以完成对这个静态方法的调用。main()方法作为我们java程序的入口,肩负着开始一段java程序的重任。由JVM这个大BOSS(“老板”)进行直接调用。将它设计成静态方法之后,当JVM想要调用它的时候就不再需要创建任何含有这个main()方法的实例。

b. 因为太底层的东西(直接操作硬件,别入编写驱动)java并不在行,因此设计相关模块的时候需要在底层调用c和C的相关模块,而我们的C和C中同样有类似的main()方法作为程序的入口,统一起来,方便调用。

c.众多周知,咱们的JVM得到的一手资料是字节码文件(.class文件)

如果不用static关键字修饰main()方法,将它声明为静态的方法。JVM就必须通过创建实例的方式来调用对应的main()方法,由于构造器可以被重载(java语言支持方法的重载(包括构造方法))JVM就无法确定调用哪个main()方法

d. 静态方法和静态数据的另一个特性就是随类加载到内存就可以直接调用而不需要像实例方法一样需要创建实例后才能调用,如果main方法是静态的,那么它就会被加载到JVM上下文中成为可执行的方法。

  1. 为什么main()方法是公有的(public)

Java语言中指定了一些可访问的修饰符如:private(私有的)、protected(受保护的)、public(共有的),任何方法或变量都可以被声明为public,Java可以从该类之外的地方访问。因为main()方法是公共的,JVM就可以轻松的访问执行它。

  1. 为什么main()方法没有返回值(void)

因为main()返回任何值对程序都没任何意义,所以设计成void,意味着main()不会有任何值返回

就比如你自己干件事,做完的时候不需要大声说我做完了一样。

注意:main()方法在Java中可以像其他方法一样被重载,但是JVM只会调用上面这种签名规范的main方法。

  1. 如果主函数是静态方法的话,根据它的特点四:在static方法中不可直接访问非static的成员。

主函数是不是不能调用非static的成员及方法?

回答是肯定的,不可以在main()方法中直接调用非静态的方法或者成员

我们的非静态方法不能直接在main()方法中进行直接的调用。这句代码的意思是创建一个User2的实例id号是101名字是高小七,使用User2类型的变量u来接收。那个指的是不能再静态方法中直接使用非静态成员变量,并不是不能声明变量,你理解跑偏了。方法内声明的变量叫作局部变量,类里面方法外面声明的变量叫做成员变量

static的应用

  1. 静态变量:保存所有对象都具有相同数据
  2. 静态函数:函数是否有访问到静态变量

静态代码块 随着类的加载而执行,先于其他,而且只执行一次,用于给类进行初始化 class demo{ static int num; static{ System.out.print("hahhahha") } static void show(){ System.out.print(num) } }

构造代码块 随着对象的创建而自动执行,可以给所有对象初始化 class demo{ { System.out.print("hahhahha") } }

可以将类的构造函数私有化,防止实例化 private ArrayTool()

其他

参数传递

public class TestUser {

	private String name;
	
	
	
	public TestUser(String name) {
		super();
		this.name = name;
		System.out.println(name);
	}

	public void transfer01(TestUser user) {
		user.name = "transfer01";
	}

	public void transfer02(TestUser user) {
		user = new TestUser("transfer02");
	}

	public static void main(String[] args) {
		
		TestUser user = new TestUser("niliv");
		user.transfer01(user);
		System.out.println(user.name);
		user.transfer02(user);
		System.out.println(user.name);
		
//		niliv
//		transfer01
//		transfer02
//		transfer01

	}

}

文档注释 javadoc -d . -author -version ArrayTool.java

单例设计模式 可以保证一个类在内存中的对象唯一性,但静态又很难释放

  1. 不允许new该类对象 私有化该类构造函数
  2. 在该类创建一个本类实例 new创建本类对象
  3. 对外提供一个方法让其他可以获取该对象 定义公有方法
//饿汉式  类一加载就有对象
public class SingleDemo {
	public static void main(String[] args) {
		Single ssSingle1 = Single.getInstance();
		Single ssSingle2 = Single.getInstance();
		System.out.println(ssSingle1==ssSingle2);  //true
	}
}
class Single{
	
	static Single single = new Single();
	private Single() {}
	public static Single getInstance() {
		return single;
	}
}
//懒汉式 类加载,没有对象 延迟加载
class single2{
    private Single2(){}

    static Single2 s2 = null;

    public static  Single2 getIn(){
        if(s2 == null)
            s2 = new Single2();
        return s2;
    }
}

Single ss1 = Single.getIn()
Single ss2 = Single.getIn()


书籍推荐