Java面向对象2

内容是根据B站宋红康老师的视频课写的,用于总结和复习.

把Java面向对象学习分为三条主线
1.类的成员:属性 方法 构造器 代码块 和内部类
2.面向对象的三大特性:封装 继承和 多态
3.其他Java关键字

写在前面,学习面向对象的时候,我很好奇有时候需要对程序入口的所在的类new 出对象,有时候又不需要.方法内可以调用方法,我们在print方法里面可以调用method2,但是在main方法里面就不能直接调用print和method2方法,这是因为static关键字....

public class Test{

    public static void main(String[] args){
        Test t1 = new Test();
        t1.print();   

    }
    public void print(){
        System.out.println("这是一个方法,写在我们创建的类里面");
    }
    public void method2(){
        System.out.println("第二个方法");
    }
}

//要new这个Test类才能成功创建对象
public class Test{

    public static void main(String[] args){
        Test1 t1 = new Test1();//现在则是new的所创建的类
        //这是创建了一个类,然后再写的方法
//上面那个demo则是把方法写在了public声明的Test类里面
        t1.print();   

    }
}

class Test1{
    public void print(){
        System.out.println("这是一个方法,写在我们创建的类里面");
    }
    public void method2(){
        System.out.println("第二个方法");
    }
}

练习 对于面向对象中类和对象的理解,并指出二者的关系.
面试中会问到这种问题,不要去说猫类狗类Person类这种案例.应该抽象一些
类:抽象,概念上的内容
对象:该类在内存中中存在的一个个个体,在程序中表现为在内存中有实体,真实地开辟了内存空间(堆).
关系:对象是由类派生出来的.

画内存图,主要注意什么时候new了那对象就应该在堆内有相应的实体,给对象一个变量名并且由首地址在栈内指向对象实体,注意到对象里面的方法是否创建了局部变量,方法调用,局部变量创建,方法结束,局部变量出栈(销毁).还应该注意到对象中是否有对象作为属性存在,有的话他们也要new出相应的实体结构,对象作为返回值得到的首地址用于引用.因为我们使用类更多的时候是使用方法,main方法结束,里面的局部变量销毁.对堆空间的实体没有引用,那么相应的实体就会被当做垃圾回收!

方法里面可以调用方法,定义局部变量和调用属性,但是不能定义方法,

关于eclpise,我们写完java程序之后,右键的run as Java application其实是把我们在控制台的javac编译和java运行这两步合二为一运行了, 也会编译生成字节码class文件,在bin目录下面.画内存的那些步骤是在运行这步做的.
使用JVM中类的加载器和解释器对生成的字节码文件进行解释运行,这意味着,需要将字节码文件对应的类加载到内存中,因而涉及到内存分析.

关于变量的分类,第一种是根据数据类型分类,基本数据类型变量和引用数据类型变量
第二种是在类中声明的位置分类

1. 成员变量
    1.1实例变量,不以static修饰
    1.2类变量,以static修饰

2.局部变量 
    2.1形参(方法,构造器参数列表中定义的变量)
    2.2方法局部变量,在方法体内定义的变量
    2.3代码块局部变量,在代码块内定义

关于"万事万物皆对象",我是没理解到的.在Python中就有这种话,我理解就是我们所用的一切的东西都是new出来的...
面试可能提到这个.1.在Java语言范畴内,我们将功能,结构等封装到类里面,通过类的实例化,来调用具体的功能结构.如Scanner;类,Arrays类,String类
2.涉及到java语言与前端的Html,后端的数据库交互的时候,前后端的结构再Java层面都体现为类/对象.
不光Java语言和Java的代码世界是这么设计的
我们在实际业务中也是这么来的,我们所处的位置是后端,对于前端,前端由Html,css和Jsp组成,这些非java语言组成的前端技术,在前后端交互的时候,前端给的数据也是类的一个对象,比如每一个标签元素反映为一个对象.和数据库交互的时候,数据库中的有表.定义一个Customer类,数据库中有相应的customer表,表中每一个横向数据,对应为一个对象.每一个纵向数据,对应为对象的属性,这样和数据交互的其实也就是对象

之前有一道对象数组的题,这里讲一下对象数组的内存解析.
 

int[] arr;
int表示数组中的元素类型,[]表示数组,1维数组, arr是数组名.


只要我们定义了类,或者java类库中有的类,我们都可以使用这种生命或者
初始化相应的类.
import java.util.Scanner;

Scanner[] scan = new Scanner[20];//这样也是可以的.

回到我们的Student类
Student[] stus;//只要创建了Student类,这样是完全可以的

stus[0] = new Student();//给数组的第一个元素初始化.实例化了Student类
变量,元素是引用数据类型,它的值只有两种,null和地址值
new了就是地址值,否则为null.

stus[1].name;//空指针异常,没有创建对象

匿名对象的使用.我们创建一个对象的时候,一般会给对象一个变量名.

Person p1 = new Person();
//我们根据p1这个变量名来操作对象的功能和结构 
p1.name;
p1.age;

但是,我们其实也可以不给变量名
new Person().sendEmail();
new Person().playGame();
就可以调用相应的功能和结构.
注意,这里每一个匿名对象都new了一次,所以他们不是同一个对象

new Phone.price =1000;
new Phone.getPrice();//打印出来是0.0,price属性的默认值,因为对象不同

关于匿名对象的理解,创建对象,但是不会显示的给一个变量名称,即为匿名对象.

匿名对象只用显示的使用一次.
开发中的使用场景,比如我们有一个类Person类,里面有个属性是个Phone对象,Phone phone;
我们给这个phone创建是时候就可以使用匿名对象

Person p1 = new Person();
// Phone p2 = new Phone();
p1.setPhone(new Phone());

//不要忘了类后面的小括号!
自定义数组的工具类,
这里是关于类的理解.

之前讲数组的时候,关于数组的操作,求数组的max min midle sum , 排序和反转,index.

当时实现了相关的功能.但是现在我们可以把这些功能实现为一个个类中的方法,使用时候就可以直接调用方法.
这里注意的是我们这些功能实现要不要返回值,返回值是什么类型.
package com.atguigu.review;

public class MyArrays {
	public static void main(String[] args) {
		double[] d1 = new double[] { 1.23, 31.2, 2.234, 2232.21, 32.1 };
		MyArrays test = new MyArrays();
		test.sort(d1);
		test.loop(d1);

	}

	// 封装到这个public类里面,在这个类里面调用
	// 假设这些操作都是对 double类型的数组进行操作的

	// 因为我们的操作都需要遍历数组,所以定义一个loop方法,不行,遍历是可以,但是返回要
	// return一出现就结束了...
	public void loop(double[] arr) {
		String end = "]";
		System.out.print("[");
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + ",");
			if (i == arr.length - 1) {
				System.out.print(arr[i] + end);
			}
		}
		System.out.println();
	}

	public double getMax(double[] arr) {
		double max = arr[0];
		for (int i = 1; i < arr.length; i++) {
			if (arr[i] > max) {
				max = arr[i];
			}
		}
		return max;
	}

	public double getMin(double[] arr) {
		double min = arr[0];
		for (int i = 1; i < arr.length; i++) {
			if (arr[i] < min) {
				min = arr[i];
			}
		}
		return min;
	}

	public double getSum(double[] arr) {
		double sum = 0.0;
		for (int i = 0; i < arr.length; i++) {
			sum += arr[i];
		}
		return sum;
	}

	public double getAverage(double[] arr) {

		return getSum(arr) / arr.length;
	}

	// 反转
	// 这里关于调用方法之后会不会有效果我还顿了一下,说明关于形参的传递机制我还没有搞清楚
	// 这里我们的形参是引用数据类型,传进去的是地址,那么反转也会在原地址上操作
	// 所以不会出现问题,调用了就会有相应的效果,而不是只作用在方法体内!
	public void reverse(double[] arr) {
		int left = 0;
		int right = arr.length - 1;
		for (; left < right; left++, right--) {
			double temp = arr[left];
			arr[left] = arr[right];
			arr[right] = temp;
		}
	}

	public void sort(double[] arr) {
		// 这里不知道数组是不是有序的.所以只能直接排序

		// 两个loop,n个元素,最大索引是length-1,外层循环n-1次
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = 0; j < arr.length - 1 - i; i++) {
				if (arr[j] > arr[j + 1]) {
					double temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}

		}
	}

}
上面的内容主要还是对之前的复习
方法的重载 (overload).定义一一个类,里面允许存在同名的方法.只要方法的参数的个数不同或者参数类型不同,关注参数列表.
关于方法重载,我们Arrays里面的sort方法有很多个,有的是对int排序,有的是对double排序.
方法重载的说明,两同一不同.
同一个方法名,同一个类,
不同的参数列表.
public void set(int i, intj){
}
public void set(int j, int i){
}//不够成重载,其实参数列表都是两个int型的变量.

下面都构成重载
public void set(int i){}
public void set(int i,double j){};
public void set(){};
private void set(char c){};

主要是关注参数列表
public void sets(){};//不构成重载,因为方法名不同...

跟权限修饰符/返回值类型/形参变量名/方法体都没关系.

编译器如何确定我们调用的是哪个方法?
1.看方法名
2.看形参列表

可变个数的形参,JDK5.0中提供了Varargs(variable number of arguments)机制,允许直接定义多个能和实参匹配的形参(0到任意个0).从而,可用更简单的方式来传递可变个数的形参

1.格式
方法名(数据类型...变量名){}

2.调用可变个数形参的方法时,实参可以传入0到任意个.

3.可变个数形参的方法与本类中其他的方法名相同时,形参不同的方法构成方法的重载

4.方法名相同,使用可变个数形参与使用数组不够成重载
public void show(String...args){}

public void show(String[] args){}//这两个不能同时存在

5.遍历可变形参
public void show(String...args){
    for (int i=0;i<args.length;i++){
        System.out.println(args[i]);
}

6.可变个数形参必须声明在参数列表的末尾,并且一个方法里面只能有1个可变个数形参.
show(int a,char c,Student...stus){
}
show(int...a,double b){}//不可以


开发中的使用场景.
在与数据库交互的时候,SQL查询id,name这些的时候,不知道具体有多少,就用可变个数形参占位

方法参数的值传递机制!!!

方法必须尤其所在类(方法中调用方法)或由对象调用才有意义.
若方法中含有参数,形参,定义在形参列表中参数,实参,调用方法的时候实际传递的参数.


我们可以对变量值进行交换.
int a =10;
int b=20;
int temp =a;
a=b;
b=temp;
//这样就实现了变量值的交换.

因为这样的操作很常用,所以我们把这一功能封装进方法里面


public void swap(int i,int j){
    int temp=i;
    i=j;
    j=temp;
}
但是,如果我们在外部调用方法的时候.
Test t =new Test();
t.swap(a,b);
//事实上a和b的值并没有发生改变.
从内存解析来看,swap方法创建了两个局部变量i和j以及temp,
j和i确实完成了交换,可以在方法内把它们打印出来,但是方法调用完成,
i,j和temp出栈,我们在外部打印a和b,仍然是最开始的a和b
但是,对于引用数据类型,
Order o1= new Order();
o1.orderId=1001;
Order o2 = o1;
o2获得了o1对象的地址,和o1共同指向同一个对象实体
o2.orderId 打印出来也是1001

o2.orderId =1;
o1.orderId与o2.orderId打印出来都是1


总结:对于基本数据类型,此时赋值的是变量所保存的值
对于引用数据类型,此时赋值是对象所保存的地址值.
值传递机制:针对基本数据类型
面试如果问到方法形参的传递机制,
回答值传递.
而不是引用传递之类的

值传递机制:
1.如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据.

因而,我们上面的swap方法没有成功达到我们的目的.

值传递机制:针对引用数据类型
那么怎么用方法成功交换a和b的值呢?


public class Test {
	public static void main(String[] args){
        Test t = new Test();
        Data d = new Data(10,20);
        t.swap(d);
        System.out.println(d.a+" "+d.b);        


    }
 
    public void swap(Data d1){//注意这次的swap方法的参数是Data对象,是引用数据类型.
//这里实参传值传的是地址,方法体的交换是根据地址做的交换,因而方法调用完出栈之后
//实际上属性在实体内真实发生交换
        int temp =d1.a;
        d1.a =d1.b;
        d1.b=temp;
    }
}
我们造一个类,只有两个属性
class Data{
    int a;
    int b;
    public Data(){}
    public Data(int a,int b){
        this.a =a;
        this.b =b;
    //还定义了一个构造器用于属性赋值
    }
}
2.如果参数是引用数据类型,那么实参实际传递的是实参存储数据的地址值.


我们在数组操作里面也经常有交换两个元素的操作,那么可以定义这样的方法

public void swap(int[] arr, int indexA, int indexB){
    int temp = arr[indexA];
    arr[indexA] = arr[indexB];
    arr[indexB] =temp;
    //这样就实现了交换
}

我们new的时候,其实是把类包括方法加载到方法区.

网红题

main方法里面有两个变量值 
int a =10;
int b =20;
t.method(a,b);
println("a ="+a);
println("b ="+b);
只能定义一个方法,让打印a是100,b是200.
如果直接写一个swap就像开头那样,方法结束就出栈
实际上下面的打印没有起到效果.

有两个思路,一个是在方法调用完直接结束程序,不执行后面的语句

public void method(int i, int j){
    i=100;
    j=200;
    System.out.println("a ="+i);
    System.out.println("a ="+j);
    System.exit(0);
    //exit方法有直接结束程序的作用
}
还有一个方法是重写打印流.



int[] arr = new int[]{12,222,33,45,6,211,233};
每一个数组元素除以数组的第一个元素,更新arr数组
for (int i=0;i<arr.length;i++){
    arr[i]= arr[i]/arr[0];
 }
这样写是错的,第一个元素被更新为1,后面全部除以1了,所以后面的元素全不变

新创建一个变量temp来保存arr[0]的话会导致更多的内存消耗

所以倒着来更新

for (int i =arr.length-1;i>=0;i++){
     arr[i]/=arr[0];
    
}

这样从后往前更新就不会像从前往后更新那样导致后面的元素不变.

上面就是内容就是关于方法的所有内容,包括之前的方法的创建,调用,方法内调用方法,方法的返回值.然后是方法重载,可变个数形参,方法的形参传递机制:值传递(包括基本数据类型和引用数据类型)...

封装性的引入
封装就是我要用一项功能,我不必知道它的内部细节,我要用洗衣机就按相应的键,用电视机也是按相应的按键.
开发给客户提供了封装好的东西,其实API提供开发者就是给开发提供了封装好的东西

关于封装.
我们程序设计追求"高内聚,低耦合"
高内聚:类的内部数据操作细节自己完成,不允许外部干涉.
低耦合:仅对外暴露少量的方法用于使用.比如Arrays类里面的sort方法里面也有交换两个变量的方法,但就没有暴露给我们.

隐藏对象内部的复杂性,只是对外公开简单的API(接口).便于外界调用,从而提高了系统的可扩展性,可维护性.
简单来说封装就是该暴露的暴露,该隐藏的隐藏,这就是封装性的设计思想.


设计一个Car类,有speed,color,wheel属性.
创建对象,赋值属性,调用方法输出,但是关于属性我们有限制
速度speed不能小于0,wheel不能是4以外的数字

所以我们创建方法,让用户调用方法来对属性初始化,
public void setSpeed(double d){
    if(d>0){    
    speed =d;
    return ;   
    }
    System.out.println("不合理的速度,赋值失败");
}

这样我们就定义好了,用户可以在外部调用方法赋值
Car c1= new Car();
c1.setSpeed(100);//这样就行了

但是,用户仍然可以在外部使用.
c1.speed=-100;//再次完成赋值,我们所做的限制也就失败了.


因而需要权限修饰符,在属性前面加上private权限修饰符,外部就不可以调用/查看属性了.
private double speed;//这样就相当于是给speed属性做了封装/隐藏.
但是,外部就不可查看speed了,因而对于private修饰的属性,我们需要暴露一个
getSpeed方法用于查看属性的值
public double getSpeed(){
    return speed;
}


....
上面就是关于封装性的引入
1.当我们创建对象以后,可以使用"对象.属性"的方式在对属性进行设置,此时,属性受到数据类型和表数范围的制约.
但是我们往往还需要加上其他限制条件,但是这些外部条件不能反映在属性中,只能通过方法来加以限制.同时,为了避免用户再次调用属性实现赋值,需要在属性前面加上private(私有的)权限修饰符.此时,针对属性就体现为封装性.

2.我们将属性私有化,同时提供公共类的方法来获取(get)和设置(set)此属性的值.(也是封装性的体现,但只是其一).
如果面试的时候问到封装性的体现,回答上面这段话,不对外暴露私有的方法,以及回答单例设计模式(构造器也私有化了).


总之从语法上记住private,get方法有返回值无参数,set方法无返回值,有参数.

关于4种权限修饰符.封装性之所以能体现,或者说是面向对象的三大特性之一,需要权限修饰符来配合.

Java所规定的四种权限(修饰符),按权限从小到大排序
private 缺省(什么都不写,英文是default) protectd public 
四种权限修饰符置于类的成员前面(目前我学到属性,方法和构造器),用于表示对象对该类成员的访问权限.

修饰符

类内部        

同一个包(package)

不同的子类

同一个工程
private可以访问
缺省   可以访问可以访问
protected可以访问可以访问可以访问
public        可以访问       可以访问可以访问可以访问

最大权限作用于同一个工程,但是我们在使用的时候也用到了比如Scanner类,Arrays类等,他们并没有定义在我们的工程下面.这是因为工程下面还有JRE包,里面就是我们长腰的jar包.
或者我们需要在不同的工程中使用,就只能复制粘贴过去...

四种权限修饰符可以用来修饰类和类的内部结构.包括内部类.
但是修饰类的话只能用缺省和public
这里有个小练习结合了封装性写get和set 包括抛出异常...
public class Student {
	public String name;
	private int age;
	
	public void setAge(int age){
		if (age<=110 &&age>=0){
			this.age =age;
			return ;
		}
		throw new RuntimeException("传入数据非法");
	}
	public int getAge(){
		return age;
	}
}

关于权限修饰符我还是不太懂,我的理解是对于类,前面要么声明为public要么声明为缺省的,缺省的只有在当前package下才能用,public在当前项目下都能用,但是还要导包import.因为目前关于方法都是用的public的,那么已经是最大权限了,能创建类,那么public方法就能用.对于属性,私有化了则在类外部都不能调用,对于缺省的属性,只能在当前包下调用,即使类时public的,属性是缺省的仍然只能在当前包下调用.如果属性是Public的,只用类是public的,能在当前项目下创建这个类,那么就能在当前项目下调用属性.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值