Java的打怪之路(二)---Java基础

(二)面向对象基础

2.1类与对象

2.1.1 类与对象的内存分析

类属于引用数据类型,所以定义类需要内存的开辟和使用,new关键字主要的功能就是开辟内存空间。
重点 内存空间的概念:
堆内存:堆内存中保存的是对象的真正数据,都是每一个对象的属性内容 要想开辟堆内存空间,就要new关键字
栈内存:栈内存保存的是一块堆内存的空间地址。

public class Demo(){
	public static void main(String args[]){
		Book bk=null; #声明对象
		bk=new Book() #创建对象
		bk.title="java"
		}	
}

如上面代码,声明对象后,栈空间内存在一个bk(地址),在创建对象时才会进入到堆内存存放对象的属性,之后给对象属性赋值。
如果使用了没有实例化的对象,直接给对象属性赋值,会报错:NullPointerException 空指向异常。原因就是没有为其开辟堆内存空间。
由此,以一个问题继续深入内存:==与equals()的区别?
1、等于关系操作符作用:
a:用于比较两个操作符的数值大小
b:用于比较两个基本数据类型的变量指向是否相同,String的直接引用类型是否指向同一个字符串。
不用于比较看似相同的两个对象,对象间的比较用equals()方法

// "=="操作符的例子
public static void main(String[] args) {
    //基本类型数据,n1和n2在栈内存中都指向5(也在栈内存),可用==比较
    int n1 = 5;
    int n2 = 5;
    System.out.println(n1 == n2);
 
    //String引用类型,s1、s2都指向常量池中的“word”,可用==比较
    String s1 = "word";
    String s2 = "word";
    System.out.println(s1 == s2);
 
    //new对象的引用integer1和integer2在栈内存引用地址不同,在堆内存中是两个对象,不可用==比较
    Integer integer1 = new Integer(3);
    Integer integer2 = new Integer(3);
    System.out.println(integer1 == integer2);
 
    //String new的对象 在堆内存中是两个对象,比较不可用==比较
    String s3 = new String("good");
    String s4 = new String("good");
    System.out.println(s3 == s4);
 
}
//输出:
true
true
false
false

2、equals的作用: equals()是Object 中的方法,用于判断两个变量是否是对同一个对象的引用,即堆中的内容是否相同。

// equals()的例子
public static void main(String[] args) {
 
    Integer integer3 = new Integer(3);
    Integer integer4 = new Integer(3);
    System.out.println(integer3.equals(integer4));
 
    //String new的对象 在堆内存中是两个对象,比较不可用==比较
    String s5 = new String("good");
    String s6 = new String("good");
    System.out.println(s5.equals(s6));
}
//输出:
true
true

2.2.2引用数据的初步分析

1、对象的引用传递
引用传递的核心在于:一块堆内存空间可以被多个栈内存空间所指向,每一个栈内存都可以修改同一块堆内存空间的属性值。

public class Book {
	String title;
	double price;
}
class TestDemo {
	public static void main(String[] args) {
		Book bookA=new Book();
		Book bookB=null;
		bookA.title="j";
		bookA.price=12;
		bookB=bookA;
		bookB.price=194;
		System.out.print(bookA.price);
		
	}
}
	输出:194
}

在这里插入图片描述
两个栈内存指向了同一个堆内存,所以改变任何一个属性,另一个属性也会发生变化
深入了解
在这里插入图片描述
如图所示,当实例化两个对象时,其栈内存分别指向不同的堆内存,但是发生引用传递时,一个栈内存指向另一个对象的堆内存,这时就会产生垃圾。一块没有栈内存指向的堆内存空间就是垃圾,这就需要垃圾回收器来进行回收
2、关于GC垃圾回收器处理。
GC垃圾回收器决定了内存的分配。当开发者创建一个对象后,GC就会监视这个对象的地址,大小和状态。对象的引用会保存在栈内存中,对象的具体内容会保存在堆内存中,当GC检测到一个堆中的某个对象不再被栈所引用,就会这个对进行不定期的回收。
垃圾处理方法:

  1. 引用计数:一个实例化对象,如果由程序使用了这个引用对象,引用计数+1,当一个独享使用完毕引用计数-1.引用计数为0时回收
  2. 跟踪收集:从root set (包括当前正在执行的线程、全局或者静态变量、JVM Handles、JNDI Handles )开始扫描有引用的对象,如果某个对象不可到达,则说明这个对象已经死亡(dead),则GC可以对其进行回收。也就是说:如果A对象引用了B对象的内存,那么虚拟机会记住这个引用路径,而如果一一个对象没有在路径图中,则就会被回收。
  3. 基于对象跟踪的分代增量收集:所有的对象回收要根据堆内存的结构划分来进行收集,具体如下
    ①基于对象跟踪:是由跟踪收集发展而来的,分代是指对堆进行了合理的划分,JVM将整个堆分为以下三代。
    A. YoungGen (新生代,使用Minor GC回收): YoungGcen 区里面的对象的生命周期比较短,GC对这些对象进行回收的时候采用复制拷贝算法。
    young: 又分为eden、survivorl ( from space)、survivort;(tosapce)。eden是在每个对象创建的时候才会分配的空间,当eden无法分配时,则会自动触发一次MinorGC。当GC每次执行时,都会将eden空间中存活的对象和survivor1中的对象拷贝到survivor2中,此时eden和survivor1的空间将被清空。当GC执行下次回收时将eden和survivor2中的对象拷贝到survivor1中,同时会清空eden和survivor2空间。按照此类顺序一次执行,经过数次回收将依然存活的对象复制到OldGen中。
    OldGen (年老代)区。
    B. OldGen(年老代, 使用Major GC回收):当对象从YoungGen 保存到OldGen后,会检测OldGen的剩余空间是否大于要晋升对象的大小,此时会有以下两种处理形式。
    1-如果小于要保存的对象,则直接进行-次 Full GC (对整个堆进行扫描和回收,但是Major GC除外),这样就可以让OldGen腾出更多的空间。然后执行MinorGC,把YoungGen空间的对象复制到OldGen空间
    1-如果大于要保存的对象,则会根据条件( HandlePromotionFailure配置:是否允许担保分配内存失败,即整个OldGen空间不足,而YoungGen空间中Eden和Survivor对象都存活的极端情况。)进行MinorGC和FullGC回收。
    C. PermGen (持久区):要存放加载进来的类信息,包括方法、属性、对象池等,满了之后可能会引起Out of MEmory错误。
    MetaSpace (元空间):持久化的替换者,直接使用主机内存进行存储。
    ②增量收集:不是每一-次都全部收集,而是累积的增量收集。

2.2封装性分析

在类中定义的属性都要求使用private声明,如果属性需要被外部所使用,你那么按照要求定义相应的getter和setter方法

2.3构造方法

  1. 构造方法和普通方法的区别:
    (1)调用时机不同,构造方法时在实例化新对象的时候只调用一次
    普通方法时实例化对象产生后,通过对象名.方法名调用多次。
    构造方法的核心作用:在类对象实例化时设置属性的初始化内容。
  2. 关于属性默认值问题:
    在定义一个类时,可以指定它的默认值,但是在在构造没有执行之前,这个默认值才会设置。
class Book{
   private String title="Java 开发"
   public Book(){}  //构造方法
}

在构造完成后,才会将java开发这个内容设置给Title

2.4匿名对象

匿名对象就是没有栈内存指向堆内存空间,这个对象智能使用一次,使用完就回收

public static void main(String[] args) {
	new Book("java",69.8)  //匿名对象的建立
}

2.5数组

  1. 数组与方法参数的传递

public class list {
	public static void main(String[] args) {
		int data[]=new int[] {1,2,3};
		change(data);
		for(int x=0;x<data.length;x++)
			System.out.print(data[x]+"、");
	}	
	public static void change(int temp[]) {
		for(int x=0;x<temp.length;x++) {
			temp[x]*=2;
		}
	
	}
}

这里将数组传给方法中的参数,这就是数组的引用传递
数组的排序、数组的转置
2. 数组的操作
(1)数组复制
System.arraycopy(源数组名称,源数组开始复制索引,目标数组名称,目标数组开始索引,长度)
(2)数组排序
java.util.Arrays.sort(数组)
3. 对象数组

class Book(){
	private double price;
}
public class Demo(){
	public static void main(String args[]){
		//动态实例化
		Book bookA[]=new Book[3];  //开辟对象数组空间 
		bookA[0]=new  Book[50];//对对象数组进行实例化

		//静态实例化
		Book bookB[]=new Book[]{
			new Book(60);
		}
	}
}

2.6String类

2.6.1字符串的比较

通过==只能比较的是数值
(1)采用构造方法实例化字符串

public static void main(Stirng arg[]){
	String stra="hello"; //直接赋值
	String strb=new String("hello"); //构造方法定义
	String strc=strb; //引用传递
	System.out.print(stra==strb); //false
	System.out.print(strc==strb);//true
}

从上述输出可以看出,==只能比较数值的大小(内存地址),而stra与strb用=比较的数值是它的地址的数值,而地址不同,通过引用传递后strc的地址与strb是相同的,所以strc=strb
只要是引用数据类型就会存在内存地址,用=比较的就是其内存地址。
如果要比较引用数据类型的内容,需要用equals()
(2)采用直接赋值实例化字符串

public static void main(Stirng arg[]){
	String stra="hello"; 
	String strb="hello"; 
	String strc="helloworld"; 
	System.out.print(stra==strb); //ture
	System.out.print(strc==strb);//false
}

采用直接赋值实例化字符串时,由于设置的内容相同,所以即使没有发生对象引用,最终的三个String对象也指向同一个堆内存空间

String两种实例化方法的区别和缺点
直接赋值(String str=“hello”):只会开辟一块堆内存空间,并且会保存在对象池中以便于下次使用。
构造方法(String str=new String(“hello”)):会开辟两块内存空间,(首先在堆内存中开辟空间保存hello,然后使用关键词new 开辟另一块堆空间。真正使用的是new开辟的空间,而之前开辟的空间会成为垃圾等待回收。)不会自动入池,但是可以通过intern()方法手工入池

public static void main(Stirng arg[]){
	String stra="hello"; 
	String strb=new String("hello");
	strb.intern();//手工入池
	System.out.print(stra==strb);//true

(3)避免空指针异常

public static void main(Stirng arg[]){
	String input=null; //直接赋值
	if(input.equals("hello")){
		System.out.print("hello");
	} //会产生空指针异常,因为input的内容是空  栈内存没有指向堆内存
	if("hello".equals(input)){
		System.out.print("hello");
	} //因为字符串常量是个String类的匿名对象所以该对象不会是NULL,所以将不会出现空指针异常
	
}

2.6.2字符串一旦被定义则不可改变

在进行String类对象内容修改时,实际上原视的字符串都没有发生变化,改变的只是String类对象的引用关系(最终没有被引用的堆空间都将成为垃圾空间)
在这里插入图片描述

public static void main(Stirng arg[]){
	String str="Hello";
	str+="World";
	str+="!!!"
}

所以在开发的过程中,尽量不要在循环语句中不断修改字符串,这样会产生很多的垃圾空间。

如果在开发中需要频繁的修改该如何解决?
回答:使用StringBuffer或StringBuilder类代替

2.6.3字符、字节与字符串的操作

(1)取指定索引字符-----charAt()方法
(2)字符数组与字符串的转换----toCharArray() String(char[] value,int offset,int count)
(3)字节数组与字符串的转换----getBytes() String(byte[]value,int offset,int count)

public static void main(Stirng arg[]){
	String str="Hello";//将大写转小写,分别使用字符串转字符数组和字符串转字节数组
	char []data=str.toCharArray();
	for(int x=0;x<data.length;x++){
		data[x]-=32;
	}
	System.out.println(String(data)); //转为字符串
	
	byte []data2=str.getByte();
	for(int x=0;x<data.length;x++){
		data[x]-=32;
	}
	System.out.println(String(data));
	
	

2.6.4字符串的查找

在这里插入图片描述
方法的使用如下面代码

public static void main(Stirng arg[]){
	String str="helloworld!:";
	System.out.println(str.contain("h")); //true
	System.out.println(str.indenxOf("l")); //2
	System.out.println(str.indexOf("l",4));//-1
}

2.6.5字符串的替换

1、public String replaceAll(String regex,String replacement) 用新的替换所有旧的内容
2、public String replaceFirst(String regex,String replacement) 替换首个满足条件的内容

2.6.6字符串的截取

1、public String substring(int beginIndex) 从指定索引处截取到结尾
2、public String substring(int beginIndex,int endIndex) 从指定开始截取到指定结束
注意:参数一定要是正整数

2.6.7字符串的拆分

1、public String[] split(String regex) 按照后面指定字符进行全部拆分
2、public String[] split(String regex,int limit) 部分拆分,最后数组长度由limit决定
注意:为了避免正则表达式的影响,对某些要进行转义

public static void main(Stirng arg[]){
		String str="张三:20|李四:21";
		String result[]=str.split("\\|"); //转义
		for (int x=0;x<result.length;x++){
			String temp[]=result.split(":");
			System.out.println("姓名"+temp[0]+"年龄"+temp[1]);
		}
}

2.7this关键字

this关键字的作用:
1、调用本类属性
2、调用本类方法
3、表示当前对象

2.7.1调用本类属性

在一些程序中,可能会出现方法参数名和属性名称重复的情况,这时就要使用this调用本类属性。

class Book(){
	private String title;
	private double price;
	public Book(String title,double price){
		//这里有一个两参数的构造方法,对对象进行实例化时,调用两参构造方法
		this.title=title;//title=title是错误的 title是构造方法中的参数
		this.price=price;
	}

public class test(){
	public static void main(Stirng arg[]){
		Book book=new Book("javakaifa",125.4);
		System.out.println(book.title)
	}
}
}

上述代码运行结果会显示书名和价格分别是null和0.0。

2.7.2调用本类方法

减少冗余代码

class Book {
    private String title;
    private double price;
    
    public Book(){
        System.out.println("需要执行50次");
    }
    public Book(String title){
        this(); //执行一个参数的构造方法调用无参构造,这样就不用在一个参数的构造方法中写重复的语句了
        this.title=title;
    }
    public Book(String title,double price){
        this(title);
        this.title=title;
        this.price=price;
    }
    
}
public class TestDemo{
    public static void main(String args[]){
        Book book=new Book("javakaifa",84);
        
    }
    
        }

注意不能再构造方法中出现嵌套的现象,就是无参调用一个参数,一个参数调用两个参数,两个参数调用无参,形成一个循环,这样就会报“构造方法递归调用”

2.7.3表示当前对象

2.8引用传递

引用传递的应用

class Person{
    private String name;
    private Car car;
    private Person child;
    public Person(String name){
        this.name=name;
    }
    public void setName(String name){
        this.name=name;
    }

    public String getName() {
        return name;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Car getCar() {
        return car;
    }

    public void setChild(Person child) {
        this.child = child;
    }

    public Person getChild() {
        return child;
    }
    public String getInfo(){
        return "人员名称"+this.name;
    }
}

class Car{
    private String cname;
    private Person person;
    public Car(String cname){
        this.cname=cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }

    public String getCname() {
        return cname;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public String getInfo(){
        return "车名称"+this.cname;
    }
}
public class TestDemo2 {
    public static void main(String args[]){
        Person p1=new Person("李华");
        Person child1=new Person("李小华");
        Car c1=new Car("奥拓");
        Car c2=new Car("劳斯莱斯");
        p1.setCar(c1);
        c1.setPerson(p1);
        child1.setCar(c2);
        c2.setPerson(child1);
        p1.setChild(child1);
        System.out.println(p1.getCar().getInfo());
        System.out.println(c1.getPerson().getInfo());
        System.out.println(p1.getChild().getInfo());
        System.out.println(p1.getChild().getCar().getInfo());

    }
}

2.9static关键字

static可以用来定义属性和方法

静态变量是随着类加载时被完成初始化的,它在内存中仅有一个,且JVM也只会为它分配一次内存,同时类所有的实例都共享静态变量,可以直接通过类名来访问它。

但是实例变量则不同,它是伴随着实例的,每创建一个实例就会产生一个实例变量,它与该实例同生共死。

所以我们一般在这两种情况下使用静态变量:对象之间共享数据、访问方便。

2.9.1static定义属性

static定义的属性是公有属性,可以由对象调用,也可以由类直接调用。调用之后改变其值。
常用的内存区域:
全局数据区:保存static类型的属性
全局代码区:保存所有的方法定义
static定义的属性和普通属性不同点:
(1)static定义的属性是公有属性,可以由对象调用,也可以由类直接调用。
(2)所有非static属性必须产生实例化对象才可以访问,而static属性不受实例化对象的控制。

2.9.2static定义方法

注意:
static方法不能直接访问非static属性或方法,只能调用static属性或方法,如果想要调用非static属性或方法,必须创建对象来调用

public class TestDemo5 {
    public static void main(String args[]){
        new TestDemo5().fun(); //fun()
    }
    public void fun(){//public static void fun()
        System.out.println("nihaoshijie");
    }
}

上述代码中 main函数是static方法,fun是非static方法 调用fun只能通过对象来调用。或者将fun改为static方法,可以直接调用
关于static方法访问限制的说明:
(1)所有的非static定义的结构,必须在类已经明确产生实例化对象时才会分配对空间,才能使用
(2)所有static定义的结构,不受实例化对象的控制,即可以在没有实例化对象的时候访问。

2.9.3static的应用

1、static计数

class Student{
    private static int num=0;
    public Student(){
        System.out.println(num+",");
        num++;
        System.out.println("number"+num+",");
    }
}

public class TestDemo6 {
    public static void main(String args[]) {
        new Student();
        new Student();
    }
}

如果num不加static 每次创建对象时 num值又变回了0

2.10内部类

定义内部类


//定义内部类 通过外部类的fun方法,访问外部类的私有属性

class outer1{ //外部类内部定义内部类
    private String msg="hello world";
    class inner1{
        public void print(){
            System.out.println(msg);
        }
    }
    public void fun(){
        new inner1().print();
    }
}
class outer2{//外部类外部定义内部类
    private String msg="hello world";
    public void fun(){
        new inner2(this).print();//this表示当前调用fun()方法的对象   主方法中由out对象调用,所以这里this就是out
    }
    public String getMsg(){
        return this.msg;
    }
}
class inner2{
    private outer2 outer2;
    public inner2(outer2 outer2){
        this.outer2=outer2;
    }
    public void print(){
        System.out.println(this.outer2.getMsg());
    }

}
//外部类访问内部类的私有属性
class outer3{
    private String msg="hello world";
    class inner3{
        private String info="hello world";
        public void print(){
            System.out.println(msg);
        }
    }
    public void fun(){
        inner3 inner3=new inner3();
        System.out.println(inner3.info);
    }
}

public class TestDemo7 {
    //outer1
    public static void main(String args[]){
        outer1 out1=new outer1();
        out1.fun();

        outer2 outer2=new outer2();
        outer2.fun();

        outer3 outer3=new outer3();
        outer3.fun();
    }
}

如何采用普通对象那样直接在外部直接产生实例化对象呢?
外部类.内部类 对象=new 外部类().new 内部类()

2.10.1使用static定义内部类

使用static定义的内部类,在创建内部类实例化对象时
外部类.内部类 对象=new 外部类(). 内部类()

class outer4{ //外部类内部定义内部类
    private static String msg="hello world";
    static class inner4{
        public void print(){
            System.out.println(outer4.msg);
        }
    }
}
public class TestDemo8 {
    public static void main(String args[]){
        outer4.inner4 inner4=new outer4.inner4();
        inner4.print();
    }
    
}

2.10.2在方法中定义内部类

在方法中定义内部类,在jdk1.8之后可以直接访问参数和变量
注意:在1.8之前,方法中定义内部类如果想要访问方法的参数或者方法定义的变量,在参数和方法之前一定要加上final标记

//在方法中定义内部类 分别访问外部类的属性、方法的参数、方法中定义的变量
class outer5{
    private String msg="hello world";
    public void fun(int num){    //final int num
        double score=99.9;       //final double score
        class inner5{
            public void print(){
                System.out.println("外部类的属性:"+outer5.this.msg);
                System.out.println("方法参数:"+num);
                System.out.println("方法变量:"+score);

            }
        }
    new inner5().print(); //内部类实例化并输出
    }

}
public class TestDemo9 {
    public static void main(String args[]){
        outer5 outer5=new outer5();
        outer5.fun(64);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值