欢迎各位大牛指教

基础概念整理

面向对象

1.什么是面向对象? 2.怎么体现? 3.特点是什么? 4.面向对象特征? 5.设计原则?

特点: 1:将复杂的事情简单化。      2:面向对象将以前的过程中的执行者,变成了指挥者。      3:面向对象这种思想是符合现在人们思考习惯的一种思想。 体现:     过程其实就是函数;     对象是将函数等一些内容进行了封装。 区别: 面向对象和面向过程的思想有着本质上的区别,作为面向对象的思维来说,当你拿到一个问题时,你分析这个问题不再是第一步先做什么,第二步再做什么,这是面向过程的思维,你应该分析这个问题里面有哪些类和对象,这是第一点,然后再分析这些类和对象应该具有哪些属性和方法。这是第二点。最后分析类和类之间具体有什么关系,这是第三点。 面向过程:强调的是功能行为,一种过程,先干啥,再干啥; 面向对象:将功能封装到对象里,强调的是具备某功能的对象; 面向对象有一个非常重要的设计思维:合适的方法应该出现在合适的类里面。 通俗理解: 透过现象看本质,传统编码习惯为描述思维,who do what ,而面向对象将问题具象化,而不仅关注当前事物表象,例如: 当我们描述一个人在吃法时,传统做法:一个人在吃饭,她是女生,穿着红色得衣服,非常beautiful... now: 我们关注对象得本质,应该有那些属性:性别,动作,长相,身高,体重等等 通过属性得set,get设置描述该事务得本质 分类: 是一个抽象的概念,它是根据抽象的原则,对客观事务进行归纳和划分,吧具体相同特征的事务(对象)归为一个类 类: 属性和行为 类是对象的抽象,对象是类的实例

如何创建一个对象? 方式: 优缺点?

1.new创建对象 Hello hello = new Hello();   

使用new关键字创建对象应该是最常见的一种方式,但我们应该知道,使用new创建对象会增加耦合度。无论使用什么框架,都要减少new的使用以降低耦合度。

2.反射机制创建对象 Class类的newInstance方法 Class heroClass = Class.forName("yunche.test.Hello"); Hello h =(Hello) heroClass.newInstance(); Constructor类的newInstance方法 //获取类对象 Class heroClass = Class.forName("yunche.test.Hello"); //获取构造器 Constructor constructor = heroClass.getConstructor(); Hello h =(Hello) constructor.newInstance();

3.采用clone clone时,需有一个分配了内存的源对象,创建新对象时,应该分配一个和源对象一样大的内存空间,调用clone方法需要实现Cloneable接口,且clone方法是protected。 public static void main(String[] args){ Hello h1 = new Hello(); try{ Hello h2 = (Hello)h1.clone(); h2.sayWorld(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } }

4.采用序列化机制 现Serializable接口,将一个对象序列化到磁盘上,而采用反序列化可以将磁盘上的对象信息转化到内存中 public class Serialize{ public static void main(String[] args){ Hello h = new Hello(); //准备一个文件用于存储该对象的信息 File f = new File("hello.obj"); try(FileOutputStream fos = new FileOutputStream(f); ObjectOutputStream oos = new ObjectOutputStream(fos); FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)){ //序列化对象,写入到磁盘中 oos.writeObject(h); //反序列化对象 Hello newHello = (Hello)ois.readObject(); //测试方法 newHello.sayWorld(); }catch (FileNotFoundException e){ e.printStackTrace(); } catch (IOException e){ e.printStackTrace(); }catch (ClassNotFoundException e){ e.printStackTrace(); } } } 创建一个对象都在内存中做了什么事情? 1:先将硬盘上指定位置的Person.class文件加载进内存。 2:执行main方法时,在栈内存中开辟了main方法的空间(压栈—进栈),然后在main方法的栈区分配了一个变量p。 3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new 4:在该实体空间中进行属性的空间分配,并进行了默认初始化。 5:对空间中的属性进行显示初始化。 6:进行实体的构造代码块初始化。 7:调用该实体对应的构造函数,进行构造函数初始化。() 8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)

类加载机制

java中的类加载器负载加载来自文件系统、网络或者其他来源的类文件。jvm的类加载器默认使用的是双亲委派模式。三种默认的类加载器Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(Application ClassLoader)每一个中类加载器都确定了从哪一些位置加载文件。于此同时我们也可以通过继承java.lang.classloader实现自己的类加载器。 Bootstrap ClassLoader:负责加载JDK自带的rt.jar包中的类文件,是所有类加载的父类 Extension ClassLoader:负责加载java的扩展类库从jre/lib/ect目录或者java.ext.dirs系统属性指定的目录下加载类,是System ClassLoader的父类加载器 System ClassLoader:负责从classpath环境变量中加载类文件 原理:当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。 具体:根据双亲委派模式,在加载类文件的时候,子类加载器首先将加载请求委托给它的父加载器,父加载器会检测自己是否已经加载过类,如果已经加载则加载过程结束,如果没有加载的话则请求继续向上传递直Bootstrap ClassLoader。如果请求向上委托过程中,如果始终没有检测到该类已经加载,则Bootstrap ClassLoader开始尝试从其对应路劲中加载该类文件,如果失败则由子类加载器继续尝试加载,直至发起加载请求的子加载器为止。 好处:采用双亲委派模式可以保证类型加载的安全性,不管是哪个加载器加载这个类,最终都是委托给顶层的BootstrapClassLoader来加载的,只有父类无法加载自己猜尝试加载,这样就可以保证任何的类加载器最终得到的都是同样一个Object对象。 缺陷: 在双亲委派模型中,子类加载器可以使用父类加载器已经加载的类, 而父类加载器无法使用子类加载器已经加载的。这就导致了双亲委 派模型并不能解决所有的类加载器问题。 案例:Java 提供了很多服务提供者接口(Service Provider Interface,SPI), 允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等, 这些SPI的接口由核心类库提供,却由第三方实现,这样就存在一个问题: SPI 的接口是 Java 核心库的一部分,是由BootstrapClassLoader加载的; SPI实现的Java类一般是由AppClassLoader来加载的。BootstrapClassLoader 是无法找到 SPI 的实现类的,因为它只加载Java的核心库。它也不能代理给 AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型 并不能解决这个问题 如果不做任何的设置,Java应用的线程的上下文类加载器默认就是 AppClassLoader。在核心类库使用SPI接口时,传递的类加载器使用 线程上下文类加载器,就可以成功的加载到SPI实现的类。线程上下文 类加载器在很多SPI的实现中都会用到。 通常我们可以通过Thread.currentThread().getClassLoader()和 Thread.currentThread().getContextClassLoader()获取线程上下文类加载器。 使用类加载器加载资源文件,比如jar包 类加载器除了加载class外,还有一个非常重要功能,就是加载资源, 它可以从jar包中读取任何资源文件

示例代码:

protected Class<?> loadClass(String name, boolean resolve){

synchronized (getClassLoadingLock(name)) { // 首先,检查该类是否已经被加载,如果从JVM缓存中找到该类,则直接返回

Class<?> c = findLoadedClass(name); if (c == null) {

try { // 遵循双亲委派的模型,首先会通过递归从父加载器开始找, // 直到父类加载器是BootstrapClassLoader为止

if (parent != null) { c = parent.loadClass(name, false);

} else {

c = findBootstrapClassOrNull(name);

}} catch (ClassNotFoundException e) {} if (c == null) { // 如果还找不到,尝试通过findClass方法去寻找 // findClass是留给开发者自己实现的,也就是说 // 自定义类加载器时,重写此方法即可 c = findClass(name); } }if (resolve) { resolveClass(c);} return c; } }

封装:

将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来对隐藏的信息进行操作和访问 1. 提高代码的安全性。 2. 提高代码的复用性。 3. “高内聚”:封装细节,便于修改内部代码,提高可维护性。 4. “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。

继承:

提高了代码的复用性,让类与类之间产生了关系,提供了另一个特征多态的前提 继承是类与类的一种关系,什么场景下适用继承? 子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用 java中的继承是单继承,即一个类只有一个父类,为什么? 若为多继承,那么当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承。 为什么是多实现呢? 通过实现接口拓展了类的功能,若实现的多个接口中有重复的方法也没关系,因为实现类中必须重写接口中的方法,所以调用时还是调用的实现类中重写的方法。那么各个接口中重复的变量又是怎么回事呢? 接口中,所有属性都是 static final修饰的,即常量,这个什么意思呢,由于JVM的底层机制,所有static final修饰的变量都在编译时期确定了其值,若在使用时,两个相同的常量值不同,在编译时期就不能通过。

方法重载:在同一个类中处理不同数据的多个相同方法名的多态手段。

方法重写:相对继承而言,子类中对父类已经存在的方法进行区别化的修改。

继承的初始化顺序   1、初始化父类再初始化子类   2、先执行初始化对象中属性,再执行构造方法中的初始化。 父类对象属性初始化---->父类对象构造方法---->子类对象属性初始化--->子类对象构造方法 注意:子类构造方法中既没有显示调用父类的构造方法,而父类没有无参的构造方法,则编译出错。 (虽然没有显示声明父类的无参的构造方法,系统会自动默认生成一个无参构造方法,但是,如果你声明了一个有参的构造方法,而没有声明无参的构造方法,系统不会默认生成无参构造方法,此时父类有没有无参的构造方法。) 成员变量 :this:代表是本类类型的对象引用。super:代表是子类所属的父类中的内存空间引用。  子类构造函数运行时,先运行了父类的构造函数。为什么呢?  原因:子类的所有构造函数中的第一行,其实都有一条隐身的语句super();  super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。  为什么子类对象初始化时,都需要调用父类中的函数?  因为子类继承父类,会继承到父类中的数据,必须要看父类是如何对自己的数据进行初始化的,所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。 super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数

问题:

super()和this()是否可以同时出现的构造函数中。   两个语句只能有一个定义在第一行,所以只能出现其中一个。   

super()或者this():为什么一定要定义在第一行?   因为super()或者this()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。

什么时候使用继承呢?当类与类之间存在着所属关系时,才具备了继承的前提,如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承

抽象类: abstract   

抽象:抽取过程中,将共性内容中的方法声明抽取,但是方法不一样,没有抽取,这时抽取到的方法,并不具体,需要被指定关键字abstract所标示,声明为抽象方法,抽象方法所在类一定要标示为抽象类,也就是说该类需要被abstract关键字所修饰。

特点:

1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。

2:抽象方法只定义方法声明,并不定义方法实现。

3:抽象类不可以被创建对象(实例化)。?为什么?

4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。 抽象类只在分配了在栈中的引用,没有分配堆中的内存。程序都有一个代码段,再内存中需要占据一定的内存,而抽象类没有具体的实现方法,无法具体的给它分配内存空间,所以为了安全,JAVA不允许抽象类,接口直接实例化 比如说让别人去买水果,不说买什么,就说买水果,显然是买不到一种叫“水果”的东西的。 一句话:有抽象方法得一定是抽象类,但是抽象类中不一定都是抽象方法 抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分

抽象类不能与哪些关键字共存,为什么?

Finally:abstract修饰的类继承后,该类的方法需要重写,而finally修饰的类不能继承,也不能有子类,方法更不能重写,相互冲突。

Private:方法子类的方法不能被继承,就不能被重写,而abstract就是要方法重写,相互冲突。

Static:static能被实例化可直接调用,abstract是不能被实例化,相互冲突   

abstract class GetTime{     public final void getTime(){   //此功能如果不需要复写,可加final限定       long start = System.currentTimeMillis();       code();   //不确定的功能部分,提取出来,通过抽象方法实现       long end = System.currentTimeMillis();       System.out.println("毫秒是:"+(end—start));     }     public abstract void code();   //抽象不确定的功能,让子类复写实现}   class SubDemo extends GetTime{     public void code(){   //子类复写功能方法       for(int y=0; y<1000; y++){         System.out.println("y");       }     }   }

多态:

函数本身就具备多态性,某一种事物有不同的具体的体现 体现:父类引用或者接口的引用指向了自己的子类对象。//Animal a = new Cat(); 好处:提高了程序的扩展性。 举例:我们能说“狗是一种动物”,但是不能说“动物是一种狗”,狗和动物是父类和子类的继承关系,它们的从属是不能颠倒的。当父类的引用指向子类的对象时,该对象将只是看成一种特殊的父类(里面有重写的方法和属性),反之,一个子类的引用来指向父类的对象是不可行的 弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性) 前提:必须要有关系,比如继承、或者实现,通常会有覆盖操作。 以前是创建对象并指挥对象做事情。有了多态以后,我们可以找到对象的共性类型,直接操作共性类型做事情即可,这样可以指挥一批对象做事情,即通过操作父类或接口实现 注意: 继承是多态的基础 1. new Object(){        void show(){          System.out.println("show run");       }     }.show(); 2. Object obj = new Object(){       void show(){         System.out.println("show run");       }     };     obj.show(); 区别? 1和2都是在通过匿名内部类建立一个Object类的子类对象,1.可编译通过,并运行。2.编译失败,因为匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提升为了Object类型,而编译时检查Object类中是否有show方法,所以编译失败

posted @ 2021-01-28 16:45  夏沫秋至  阅读(103)  评论(0编辑  收藏  举报