-
相同
- 不能实例化
- 可以将接口类、抽象类作为引用类型
- 一个类如果继承了某抽象类或接口;需要对其中的抽象方法全部实现,否则该类仍为抽象类
-
不同:
-
抽象类:
- 可以定义构造函数(构造器)
- 可以有抽象方法、具体方法、可包含静态方法
- 抽象类中成员是private、protected、public、默认
- 可定义成员变量
- 可以没有抽象方法
- 一个类只能继承一个抽象类
-
接口
- 不能定义构造函数(构造器)
- 都是抽象方法,不能有静态方法
- 接口的成员都是public
- 接口定义的成员变量都是常量
- 一个类可以实现多个接口
-
-
什么时候用接口或抽象类
- 接口:
- 接口是事物共有特征的集合
- 抽象类“
- 概念: 一类事物:人类,动物;植物
- 接口:
-
重写:
- 子类将父类的方法改写
- 方法名、返回类型、参数须与父类一致
- 或子类的返回类型是父类返回类型的子类
-
重载
- 一个类中,同名方法
- 不同的参数列表:
- 参数类型,个数,顺序不同
- 返回类型没要求;可相同也可不同
- 不同的参数列表:
- 一个类中,同名方法
八种数据变量中直接存储值
byte 、short、Long、int、float、double 、Boolean、char
八大基本数据类型对应着各自的封装类型,提供了更多的方法,且不进行初始化时值默认为空(基本数据类型必须初始化)
像String类型属于引用类型,变量中存储的是地址,对应的地址存储数据
==:
- 如果是基本数据类型的变量,则判断两者的值是否相同
- 引用类型的变量: 判断两者对象的地址是否一致
equals:
- equals 不能用于基本数据类型
- equals 继承Object类。比较的是 是否为同一对象
- 若未对equals 进行重写, 比较的是 引用类型的地址
- 若重写;则比较对象的内容
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date、File、包装类等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
false,不同对象,其地址不同
true ,值相同
true,基本类型,==判断值
包装数据类型间比较,
equals比较首先比较包装类型是否相同,如果相同,再比较值是否相同,都相同则为true,反之为false
Integer:如果传入的两个值在-128~127之间,那么会把这两个一样的数值放在一个叫常量池的东西里,那么这个时候两个数值比较就是true了
引用类型:
String的equals判断值:String类型之所以equals会比较值内容是因为它重写了
例子:
byte b1 = 127;
Byte b2 = new Byte("127");
Byte b3 = new Byte("127");
System.out.println("Byte 基本类型和包装对象使用 == 比较 : " + (b1 == b2));
System.out.println("Byte 基本类型和包装对象使用 equals 比较 : " + b2.equals(b1));
System.out.println("Byte 包装对象和包装对象使用 == 比较 : " + (b2 == b3));
System.out.println("Byte 包装对象和包装对象使用 equals 比较 : " + b2.equals(b3));
Byte 基本类型和包装对象使用 == 比较 : true : 拆箱
Byte 基本类型和包装对象使用 equals 比较 : true
Byte 包装对象和包装对象使用 == 比较 : false, 不同对象
Byte 包装对象和包装对象使用 equals 比较 : true
- 易错:
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);//true
System.out.println(i3 == i4);//false Integer缓存-128到127
Double d1 = 100.0;
Double d2 = 100.0;
Double d3 = 200.0;
Double d4 = 200.0;
System.out.println(d1 == d2);//false
System.out.println(d3 == d4);//false
/ 在某个范围内的整型数值的个数是有限的,而(Double、Float)浮点数却不是。
throw与throws的区别注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
Double、Float的valueOf方法的实现是类似的。
- throw是语句抛出异常,出现于函数内部,用来抛出一个具体异常实例,throw被执行后面的语句不起作用,直接转入异常处理阶段,
- throws:throws是函数方法抛出异常,一般写在方法的头部,用来抛出一些异常,本身不进行解决,抛给方法的调用者进行解决
-
String 是只读字符串,(final类型的字符串数组)一旦创建就不可更改
- 每次创建都是新的String对象
-
StringBuilder、StringBuffer 是可对字符串内容修改;且内存地址不变
- StringBuilder 线程不安全;没有加锁
- StringBuffer :对其方法加了 synchronized同步锁,线程安全
性能/ 效率: StringBuilder >StringBuffer > String
优先使用StringBuilder ;多线程使用共享变量时使用StringBuffer
声明一个类不会被继承该类被final修饰,则此类不能有子类,不可被其他类继承
如:Math类
生成对象内存布局:cat指明一地址,在堆中存储对象cat的数据。
-
属性:
- 12是基本数据类型,直接存储
- String字符串是引用类型;需将其存至常量池,由地址调用
-
Java 内存的结构分析
- 栈: 一般存放基本数据类型(局部变量)
- 堆: 存放对象(Cat cat , 数组等)
- 方法区:常量池(常量,比如字符串), 类加载信息
new(创建对象) 时: Cat c = new Cat();
- 类加载器,加载cat类的信息(属性、方法);只会加载一次
- 在堆中分配空间、对cat类进行默认初始化(: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char u0000,boolean false,String null),把堆的地址赋给c
- 进行指定初始化:如:p.name =”jack” p.age = 10
- 当方法被调用时;会在栈里独立分配一个空间A
- 方法结束后;将结果返回至调用方法的地方
- 销毁栈空间A。
- main栈结束后也销毁。整个程序退出
public class MethodParameter01 {
//编写一个 main 方法
public static void main(String[] args) {
int a = 10;
int b = 20;
//创建 AA 对象 名字 obj
AA obj = new AA();
obj.swap(a, b); //调用 swap
System.out.println("main 方法 a=" + a + " b=" + b);//a=10 b=20
}
}
class AA {
public void swap(int a,int b){
System.out.println("na 和 b 交换前的值na=" + a + "tb=" + b);//a=10 b=20
//完成了 a 和 b 的交换
int tmp = a;
a = b;
b = tmp;
System.out.println("na 和 b 交换后的值na=" + a + "tb=" + b);//a=20 b=10
}
}
执行obj.swap(a, b); 时,会另开辟一个栈空间,与main栈相互独立,且期间并无返回值
故没有交换
- 结论:
- 基本数据类型传递的是值;形参不会影响到实参
引用类型传递的是地址(地址的值);可通过形参影响实参
两个独立栈空间里的arr数组都指向同一个对象
- 引用类型也是值传递,这里的值是地址的值,也就是指针的值
public class a {
public static void main(String[] args) {
Person p = new Person();
p.name = "jerry";
p.age = 3;
B b = new B();
b.test200(p);
System.out.println(p.age+"=||"+p.name);
}
}
class Person {
String name;
int age;
public Person(String name) {
this.name = name;
}
}
class B {
public void test200(Person p) {
// 思考 p.age = 10000; //修改对象属性
//思考
// p = new Person();
// p.name = "tom";
// p.age = 99;
//思考
p = null;
// 思考
Person person = new Person("李四");
p = person;
}
}
-
p.age = 10000; 将传来的p的属性修改;此时用的是同一对象 (引用对象传递地址) 故改变
-
p = new Person();
p.name = “tom”;
p.age = 99; 此时在堆中另创了对象 方法执行结束,这个p就消失了,与main栈无关, 故不变 -
p = null; 此时将传来的地址设为空,方法执行结束,这个p就消失了。与main栈无关
置空相当于把指向的箭头去掉,而不是把数据抹掉。
-
p = person; 这个和 第二个一样,这个p就消失了
总结:
-
引用类型参数 只是复制了一份,除了setName() 和 设置属性生效外,其余都不影响实参本身
-
方法执行完后栈空间copy的那个p引用变量消失(如果没有其他引用指向它,它就会等待垃圾回收器的回收),回到主方法后,首次声明的p引用依然指向原来的p对象。
-
String与 基本数据类型 的类似
- 因为:String对象一旦创建,内容不可更改。每一次内容的更改都是重现创建出来的新对象。
- 存在多个方法的名、功能、参数属性一致时, 可封装成一个方法:
public int sum(int... nums) {
System.out.println("接收的参数个数=" + nums.length);
}
- int... nums 表示 可输入多个int参数
- 可变参数本质上是 数组
- 可变参数与其他参数都放在形参列表时,可变参数必须放在最后
public String getInfo(String name,int... nums) {
System.out.println("接收的参数个数=" + nums.length);
}
- 形参列表只能出现一个可变参数
//细节: 一个形参列表中只能出现一个可变参数
//下面的写法是错的.
public void f3(int... nums1, double... nums2) {
}
作用域
变量分为: 成员变量 、 局部变量
-
成员变量: 也叫全局变量。类的属性,Static修饰成为类变量或静态变量,还有就是方法外的变量。等
- 作用域:作用于整个类
- 可以不赋值,直接使用,因为有默认值,
-
局部变量: 一般是 在成员方法中定义的变量 a 、除属性外的其他变量如:代码块的区域创建的变量
- 作用域:方法内部 、
- 必须赋值,没有默认值
cat类中方法 ha 的变量 a
public void ha(){ int a = 0; }
注意事项
-
成员变量与局部变量可以重名;访问符合就近原则
-
同一作用域中,变量不能重名
-
是
-
成员变量可以被其他类访问;局部变量不行
-
成员变量可以加 修饰符 ;局部变量不行
-
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。
-
局部变量,生命周期较短,伴随着它的代码块的执行而创建,
伴随着代码块的结束而销毁。即 在一次方法调用过程中 生效
构造器是初始化对象;并不是创建对象
-
在创建对象时,会自动调用构造器方法去初始化对象
-
如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器)
-
一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器, 除非显式的定义一下,
class Person {
String name;//默认值 null
int age = 11;//默认 0
//第一个无参构造器:利用构造器设置所有人的 age 属性初始值都为 18
public Person() {
age = 18;//
}
//第二个带 pName 和 pAge 两个参数的构造器
public Person(String pName, int pAge) {
name = pName;
age = pAge;
}
}
Person p = new Person("预习",20);
- 加载类的信息 (只加载一次)
- 在堆中开辟新的空间;
- 对象初始化:
- 且进行默认初始化 (属性的初始值0、null)
- 进行显式初始化 (null、11)
- 构造器初始化 (“预习”,20)
- 将对象在堆中的地址 指向 p
java虚拟机会给每个对象分配一个this
this : 表示当前对象。 作用域: 只能在类定义的方法中使用。
- this可访问 类的 属性、方法、构造器
- this:用于区分 构造器中的 局部变量和成员变量
public Person(String name, int age) {
this.name = name; this.age = age;
}
// 区分 形参的name 和 类的属性的name
- this访问构造器
- this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器
- 必须放在第一条语句
public Person(){
this("㔿",13);
sout("123");
}
public Person(String name, int age) {
this.name = name; this.age = age;
}
包
- 作用:
- 区分同一名称的类
- 管理类
- 控制访问的范围
- 本质:创建不同的文件夹 / 目录 保存类
java中的包和操作系统没有任何关系,java的包是用来组织文件的一种虚拟文件系统。
不允许包名以java开头,否则会导致命名空间污染的问题
访问修饰符类的属性、方法符合以下规则
公开: public: 对外公开
受保护: protected: 对子类、同一包下公开
默认: 无修饰符号, 对同一包下公开
私有: private: 对类本身公开
- 修饰符可 修饰 类、类的属性,成员方法
- 默认、public 修饰类
public class A {
//四个属性,分别使用不同的访问修饰符来修饰
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public void m1() {
//在同一类中,可以访问 public protected 默认 private 修饰属性和方法
System.out.println("n1=" + n1 + " n2=" + n2 + " n3=" + n3 + " n4=" + n4);
}
}
package com.hspedu.modifier;
public class Test {
public static void main(String[] args) {
A a = new A ();
a.m1();
}
}
为什么Test可以访问n4?
Test测试类是调用对象内的方法,而A对象方法m1是公开的public
调用本类中的四个访问修饰符变量,所以可以访问
面向对象的三大特征面向对象编程有三大特征:封装、继承和多态
封装把对象的属性和方法结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口。
继承的细节继承后,类加载先加载父类的信息
A 继承B后; A就拥有B的属性+方法;所有的子类不需要重新定义这些属性和方法
-
子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
-
子类必须执行父类的构造器,以初始化父类
-
子类创建的时候会自动调用父类的无参构造器
- 如果父类没有无参构造器,则需要子类在构造期中去写父类的其他构造器 super
-
super 仅用于构造器中,必须放在构造器第一行,与this类似
-
子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
- 如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
-
不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
继承的本质:
先看子类
(1) 首先看子类是否有该属性 (2) 如果子类有这个属性,并且可以访问,则返回信息 (3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..) (4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object...多态
多态是建立在封装和继承基础之上的。
重载、重写就是多态的体现
多态前提: 两个类存在继承关系
向上转型: 父类的引用 指向了子类对象
父类类型引用名 = new 子类类型();
- 可以调用父类的所有可访问的方法、属性及子类重写的方法
- javac 编译: 仅能 识别使用父类类型的成员;
- 不能调用子类的特有的成员
- 运行时,看子类的的具体实现: 从子类开始(初始化)查找
- 编译类型看左边;运行类型看右边
- 条件:
- 要求父类的引用必须指向的是当前目标类型的对象
- 就是:目标类型先进行向上转型,
- 此时可以调用子类所有的成员
Animal animal = new Cat(); Cat cat = (Cat) animal; // 父类的引用
编译类型:cat; 运行类型:cat (强转了)
此时可以调用子类的成员
java 的动态绑定机制- 调用对象方法: 该方法会与该对象的内存地址(运行类型)绑定
- 也就是 尽管方法名同名,也会调用该对象的方法
- 调用对象属性时: 哪里声明,哪里使用
- 动态绑定只是针对对象的方法,对于属性无效。因为属性不能被重写。
java动态绑定机制 - 捞月亮的渔夫 - 博客园 (cnblogs.com)
Java的动态绑定机制 - eric.dream - 博客园 (cnblogs.com)
原来也有 对象数组
数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用
double scores[] = new double[5]; Person p[] = new Person[5];hashCode
返回该对象的哈希值
-
两个引用指向同一对象;其哈希值一样
-
两个引用,指向不同对象;哈希值不一样
- 但 两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
以后看到 集合再补
- 假如只重写equals而不重写hashcode,那么Student类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,显然此时s1!=s2,故两者的hashcode不一定相等。
- 然而重写了equals,且s1.equals(s2)返回true,根据hashcode的规则,两个对象相等其哈希值一定相等,所以矛盾就产生了,因此重写equals一定要重写hashcode,而且从Student类重写后的hashcode方法中可以看出,重写后返回的新的哈希值与Student的两个属性有关。
finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
对象可以被回收,就代表一定会被回收吗?
对于快要dead的线程:
dead的线程
- 利用finalize()方法最多只会被调用一次的特性,我们可以实现延长对象的生命周期。
- 重写finalize()方法意味着延长了回收对象时需要进行更多的操作,从而延长了对象回收的时间。
-
对象被回收时,系统会自动调用该对象的 finalize方法 去释放一些资源;子类可重写该方法
-
即使垃圾回收器工作,finalize方法也不一定得到执行,这是由于程序中的其他线程的优先级远远高于执行finalize()函数线程的优先级。
-
什么时候被回收:
Car c = new Car(); c = null;
-
当某个对象没有任何引用时,jvm会将该对象视为垃圾对象,使用垃圾回收机制来销毁对象。
-
销毁对象之前会调用该对象的finalize()方法
-
如果没有重写,则默认调用Object的finalize()方法:
-
protected void finalize() throws Throwable { } -
finalize()方法只有一个****方法体*,里面*没有代码*,是由*protected修饰****的
-
-
-
垃圾回收机制的调用: 由系统(GC算法)决定; 有了垃圾并不会立即回收
- 主动触发机制: System.gc();
类变量: 静态变量。在类加载时就初始化了,没创建对象也能使用类变量
类方法: 静态方法。不能使用对象的关键字:this,super等
person.age 对象引用.静态变量
- 只能调用静态变量和其他类方法
- 格式是固定的。
- 被 jvm所识别和调用。
public static void main(String[] args)
public: 要让jvm识别到所以权限必须是最大的。
static: 不需要创建对象即可访问main方法。
void: 主函数没有具体的返回值。
main:函数名,不是关键字,只是一个jvm识别的固定的名字。
String[] arg: 这是主函数的参数列表,是一个数组类型的参数,而且元素都是字符串类型的。
- 默认Java虚拟机创建了new String[0]作为传入参数,长度为0。
- main函数为什么要传入字符串呢?
- 因为任何数据都可以通过字符串形式的数据进行转换。、
什么时候类加载:
- 创建对象new
- 创建子类对象实例,先加载父类的信息
- 使用类的静态成员(静态属性、静态方法)
代码块优先于构造方法的执行
普通代码块:
- 创建对象时被隐式调用; 创建一次,执行一次
静态代码块:
- 在类加载时执行,且仅执行一次
- 作用:对类初始化
被final修饰的属性 必须赋初值;且不能被修改
final 的类不能被继承,能实例化
final的方法不能被重写,能继承
forfor(int i = start, j=end; i循环初始值可以有多条初始化语句,但要求类型一样,并且中间用逗号隔开,
中间是布尔表达式
循环变量迭代也可以有多条变量迭代 语句,中间用逗号隔开。
String的两种方式



