目录
1.面向对象的特征
封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。如私有的成员变量就是封装的体现。将变化隔离;便于使 用;提高重用性;安全性。
继承:java中对于继承,java只支持单继承。但是java支持多重继承。多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。继承的时候需要注意访问控制符,默认的访问控制符是包访问权限,继承其他包的类,只能访问其public的成员,所以为了继承一般的规则是将数据的成员设为private,方法设为public。对基类的初始化只有一种方法,就是在构造器中调用基类的构造器来执行初始化,基类构造器具有执行基类初始化的所有知识和能力,java会自动在导出类的构造器中插入对基类构造器的调用。
多态:函数本身就具备多态性,某一种事物有不同的具体的体现。
继承的时候要注意的点:
1:子类覆盖父类时,必须要保证,子类方法的权限必须大于等于父类方法权限可以实现继承。否则,编译失败。
2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)
2.抽象类: abstract
由于抽象类创建对象是不安全的,所以抽象类不可实例化,如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义,如果不这样做,那么导出类也是抽象类,并且要加上abstract这个关键字。抽象类有这样的一种用途,我们想阻止产生这个类的任何对象。抽象类是很有用的重构工具,因为他们使得我们可以很容易的将公共方法沿着继承层次结构向上移动。
抽象类的特点:
1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。
2:抽象方法只定义方法声明,并不定义方法实现。
3:抽象类不可以被创建对象(实例化),但是他有构造函数,用于给子类对象初始化的。
4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。
其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。
3.接口
1.接口所有的方法默认是public,
2.bing接口中可以定义常量,目的就是分组,并且默认的修饰符是public static final.
3.java8之后可以在接口中提供简单的方法(静态方法)
抽象类和接口的使用原则:抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。当2个或多个类中有重复部分的时候,我们可以抽象出来一个基类,如果希望这个基类不能被实例化,就可以把这个基类设计成抽象类。
接口可以包含一些域,但是这些域隐式的是static 和final的。所以接口就很便捷的成为了一种用来创建常量组的工具,这些域不是接口的一部分,他们被存在了该接口的静态存储区域内。
- 接口的使用方式1:解耦
创建一个能够根据所传递的参数对象不同而具有不同行为的方法,被称为策略模式。
public class Processor {
public String name(){
return getClass().getSimpleName();
}
Object process(Object o){
return o;
}
}
public class Upcase extends Processor {
String process(Object input) {
return ((String) input).toUpperCase();
}
}
public class Downcase extends Processor {
String process(Object input) {
return ((String) input).toLowerCase();
}
}
public class Splitter extends Processor {
String process(Object input) {
return Arrays.toString(((String) input).split(" "));
}
}
public class Apply {
public static void process(Processor p, Object o) {
System.out.println("Using Process " + p.name());
System.out.println(p.process(o));
}
public static void main(String[] args) {
process(new Upcase(), "To change this template use File");
process(new Downcase(), "To change this template use File");
process(new Splitter(), "To change this template use File");
}
}
但是这样耦合过紧,Apply.process()方法无法复用,如果Processor是一个接口,就可以完全解耦了。
Processor变成接口:
public interface Processor {
String name();
Object process(Object o);
}
再来两个抽象类,分别处理String类型和Integer类型,抽象类将公共的方法提取出来了
public abstract class StringProcessor implements Processor {
@Override
public String name() {
return getClass().getSimpleName();
}
public abstract Object process(Object o);
}
public abstract class IntegerProcess implements Processor {
@Override
public String name() {
return getClass().getSimpleName();
}
public abstract Object process(Object o);
}
实现类:
public class Upcase extends StringProcessor {
public String process(Object input) {
return ((String) input).toUpperCase();
}
}
public class Splitter extends StringProcessor {
public String process(Object input) {
return Arrays.toString(((String) input).split(" "));
}
}
public class Downcase extends StringProcessor {
public String process(Object input) {
return ((String) input).toLowerCase();
}
}
public class AutoNumber extends IntegerProcess {
@Override
public Integer process(Object o) {
return (Integer) o + 5;
}
}
测试:
public class Apply {
public static void process(Processor p, Object o) {
System.out.println("Using Process " + p.name());
System.out.println(p.process(o));
}
public static void main(String[] args) {
process(new Upcase(), "To change this template use File");
process(new Downcase(), "To change this template use File");
process(new Splitter(), "To change this template use File");
process(new AutoNumber(), 5);
}
}
上面就完美了吗?没有。组合拳还没有结束,因为我们经常碰到的情况是无法修改你想要的使用类,例如下面的这个关于Long类型的处理,他没有接口:
public class LongProcess {
public String name() {
return getClass().getSimpleName();
}
public Long process(Long value) {
return value;
}
}
public class LongAdd extends LongProcess {
public Long process(Long value) {
return value + 5L;
}
}
在这种情况下,可以使用适配器模式,适配器中的代码将接受你所拥有的接口,并产生你所需要的接口。这里面还用到了代理。
如果不用代理,可以用继承:
public class LongAdapter extends LongAdd implements Processor {
// private LongProcess longProcess;
//
// public LongAdapter(LongProcess longProcess) {
// this.longProcess = longProcess;
// }
@Override
public String name() {
return super.name();
}
@Override
public Object process(Object o) {
return super.process((Long) o);
}
}
结果还是一样的:
接口是实现多重继承的途径,而生成某个接口的对象的典型方式就是工厂方法设计模式,这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现对象。
- 接口的使用方式2:创建常量的工具
虽然写起来更方便,不用public static final,但是网友说不用,还是建议使用枚举和常量类
4.初始化
想一下初始化一个java的类,方式有哪几种?第一种,数据域显示赋值,除非所有的实例对象的这个数据域都有相同的值,否则不要这样做。第二种方式,通过构造器赋值,使用this调用构造器消除重复的代码。第三种就是构造代码块,只要构造类的对象,这些代码块就会被执行。所有的静态初始化语句,以及静态初始化代码块都将依照类定义的顺序执行。
1.初始化的顺序 : 静态优先父类优先
普通类的初始化顺序
静态属性:static 开头定义的属性
静态方法块: static {} 圈起来的方法块
这里如果有main函数,则要在这里执行main函数,把main函数看场子类的静态方法就可以了。
其他静态方法
普通属性: 未带static定义的属性
普通方法块: {} 圈起来的方法块
构造函数: 类名相同的方法
方法: 普通方法
父类优先的原因:子类继承到了父类的数据,必须看父类的数据如何构造的。子类的所有构造函数中的第一行,其实都有一条隐身的语句super(),super()代表是子类所属的父类中的内存空间引用,也是在子类中调用父类方法和属性的方式。
5.构造函数
用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。该函数的名称和所在类的名称相 同。不需要定义返回值类型(那么如果有返回值就是普通的方法了)。所有的对象创建的时候都需要初始化才能使用。构造 器能保证正确的初始化和清理,(没有正确的构造器调用,编译器就不允许创建对象),所以有了完全的控制也很安全。构 造器可以有多个,并且创建对象一定会调用构造器,具体看调有参的还是无参数的而已。
在书写子类的构造方法的时候需要注意一下几点:
- 子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();
- 如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数。
- 如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。
6.成员变量和局部变量的区别:
首先他们的作用域不同,其次他们存放的位置不同,java里基本数据类型存在栈上,boolean,short,int,char,float,double,long,byte这8种,其余所有都是引用类型,都存在堆上,局部变量存在于栈内存中,成员变量存在堆内存中。值,编译器就会报方法的成员变量如果不赋错,如果是类的成员变量,编译器就会给默认的值。这叫自动初始化,就算通过构造器来初始化自动初始化还是会进行,他会在调用构造器初始化之前发生。
7.构造代码块和构造函数有什么区别?
构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块。
构造函数:是给与之对应的对象进行初始化。它具有针对性。
8.this关键字(三个用法)
同一个类的不同对象调用同一个方法,这个方法是如何知道哪个对象调用的他呢?编译器默认就会传一个this过去,所谓“面向对象发送消息”。
在方法的内部调用同一个类的另一个方法,就不必使用this,this用在该用的地方,比如在一个类中返回当前的对象。直接return this,this关键字也可以将当前的对象传递给其他类方法。去执行由于某种原因而不得不放在该类外面执行的方法,可能仅仅是你的提取出来的公共代码。再比如说在构造器中调用构造器以避免重复的代码。但是需要注意,不能同时调用两个构造器,否则会报错。
构造器是用来初始化的,编译器会禁止在任何方法中去调用构造器
开发时,什么时候使用this呢?在定义功能时,如果该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。
this 还可以用于构造函数间的调用。 调用格式:this(实际参数);
用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。
super()和this()是否可以同时出现的构造函数中?两个语句只能有一个定义在第一行,所以只能出现其中一个。
9.final关键字
不改变的理由是设计或者效率,final在编译时执行运算,减轻了运行时的负担,但是并不说使用了final在编译期就可以知道它的值, final数据,final用于常量之上,final大都应用于基本类型,或者不可变对象(如果类中的每一个方法都不改变其对象,那么这个类可以设计为final类),并且必须赋初值。final指向可变对象也是可行的,但是容易造成误解。如果要在一个类中定义一个域为final,那么必须直接初始化它或者在多有的构造函数中去给他赋值,并且他只能提供get访问器方法。
这个数值恒定不变的理解是怎么样的呢?是指在比如这个类初始化的时候,给这个常量赋上一个随机值,那么再次初始化这个类的时候这个常量的值还是上次随机得到的那个值,这个怎么去和static修饰的比较注意
一个既是static又是final修饰的域只占据一段不能改变的地址空间。他在装载的时候已经被初始化,再次创建对象不会再次被初始化。
对于引用对象final使引用恒定不变,不能认为他是final就无法改变它的值,他只是无法将这个引用再次指向一个新的对象而已。同样适用于数组。就是说在初始化的时候他已经指向一个引用,再new一个对象让他去指向是不行的。
继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。
这时如何解决问题呢?介绍一个关键字,final:最终。
final特点:
1:这个关键字是一个修饰符,可以修饰类,方法,变量。
2:被final修饰的类是一个最终类,不可以被继承。
3:被final修饰的方法是一个最终方法,不可以被覆盖。
4:被final修饰的变量是一个常量,只能赋值一次。
10.static关键字
static修饰的方法会和类失去关联,static方法就是没有this的方法。在static方法的内部不能调用非静态的方法。反过来就是可以的,不创建对象仅通过类本身来调用static方法,这是static方法的主要用途。静态代码代码块和非静态代码块区别?非静态代码块会在每次类被调用或者被实例化时就会被执行。静态代码块类似于静态变量,不论类被调用多少次,该区域代码只在第一次时执行一次。静态代码块是在类加载时自动执行的,非静态代码块是在创建对象时自动执行的代码,不创建对象不执行该类的非静态代码块。
特点:
想要实现对象中的共性数据的对象共享。可以将这个数据进行静态修饰
静态随着类的加载而加载。而且优先于对象存在。
注意:
静态方法只能访问静态成员,不可以访问非静态成员。this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西。因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。
静态方法中不能使用this,super关键字。因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。
成员变量和静态变量的区别:
1,成员变量所属于对象。所以也称为实例变量。静态变量所属于类。所以也称为类变量。
2,成员变量存在于堆内存中。静态变量存在于方法区(永久区)中。
3,成员变量随着对象创建而存在。随着对象被回收而消失。静态变量随着类的加载而存在。随着类的消失而消失。
4,成员变量只能被对象所调用 。静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。
11.数组的初始化
基本数据类型数组:其中存储的元素为基本类型数据。
引用类型数组:元素是对象,其中存储的是对象的地址值。 引用数据类型的数组使用规定长度的方式进行初始化时,默认值为null。如:Cell[] cs = new Cell[10];cs中存有数组对象的地址,此对象中有10个null。
当java5引入可变参数后,就再也不用显示的编写数组的语法了。当你指定参数时,编译器实际上会为你去填充数组。你获得的仍旧是一个数组。所以才可以用foreach来迭代参数列表。
上面是将数组转换为可变的参数,下面将将可变的参数变为数组
args.getClass(),他将产生对象的类,学习类型信息的时候再来看这里。
12.创建一个对象都在内存中做了什么事情?
Person p = new Person();
1:先将硬盘上指定位置的Person.class文件加载进内存。
2:执行main方法时,在栈内存中开辟了main方法的空间(压栈—进栈),然后在main方法的栈区分配了一个变量p。
3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。()
8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)
13.初始化的总结
方法的成员变量如果不给初始化会报错,但是类的成员变量是基本类型会默认给初始的值。如果是对象的引用就会是null.
构造器初始化: 类成员变量的初始化是在构造器被调用之前发生的。
初始化的顺序,在类的内部,变量定义的先后顺序,决定了初始化的顺序,他们会在任何方法,包括构造器被调用之前得到初始化,即使他们散布于方法定义之间。
静态数据的初始化:static不能用作局部的变量(这个不难理解,因为static作用的域是和类无关的),只能作用于域,如果一个域是静态的基本类型域,则他会获得基本类型的标准初始值,如果他是一个引用,这默认为nuLL
要加载main()方法,必须先加载他的这个类,加载这个类就会加载他的静态域,然后加载成员变量,然后是构造函数。静态域只会被加载一次。
静态数据的初始化栗子:看代码,猜打印
public class Test7 {
public static void main(String[] args) {
System.out.println("new Cupborad");
new Cupborad();
System.out.println("new Cupborad");
new Cupborad();
table.f2(1);
cupborad.f3(1);
}
static Table table=new Table();
static Cupborad cupborad=new Cupborad();
}
class Bowl{
public Bowl(int marker){
System.out.println("Bowl:"+marker);
}
public void f1(int marker){
System.out.println("f1:"+marker);
}
}
class Table{
static Bowl bowl1=new Bowl(1);
public Table(){
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int market){
System.out.println("f2:"+market);
}
static Bowl bowl2=new Bowl(2);
}
class Cupborad{
Bowl bowl3=new Bowl(3);
static Bowl bowl4=new Bowl(4);
public Cupborad(){
System.out.println("Cupborad");
bowl4.f1(2);
}
void f3(int market){
System.out.println("f3:"+market);
}
static Bowl bowl5=new Bowl(5);
}
结果:
数组的初始化:
数组的定义方式 两种,初始化数组的时候都是相当于new的操作,直接赋值也是相当于new的一个操作,所以数组的new出来的数据应该在堆上面放着,而数组的引用应该在栈上面放着,所以数组的引用赋值给另一个引用的时候后他们指向的内存空间是一样的,即操作的数据是一样的。
14.equals()与hashCode()
equals与hashCode是Object类中的两个方法,equals是判断两个对象是否具有相同的引用,而hashCode是有对象导出的一个整型值,不同的类导出的方式可能不一样,比如String类他是根据内容来导出的。StringBuilder是根据地址来导出的。
我们自己如何实现重写这两个方法呢?
可以利用编译器自动生成
package com.wx1.test4;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* User: Mr.Wang
* Date: 2019/10/27
*/
public class Employee {
private String name;
private Double salary;
private LocalDateTime hireDay;
public Employee(){
}
public Employee(String name, Double salary, LocalDateTime hireDay) {
this.name = name;
this.salary = salary;
this.hireDay = hireDay;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public LocalDateTime getHireDay() {
return hireDay;
}
public void setHireDay(LocalDateTime hireDay) {
this.hireDay = hireDay;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;//优化
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(salary, employee.salary) &&
Objects.equals(hireDay, employee.hireDay);
}
@Override
public int hashCode() {
return Objects.hash(name, salary, hireDay);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
}
15.值传递
一个方法不可能修改一个基本数据类型的参数
证明值传递的例子: