文章目录
this
在类方法中,使用this关键字调用当前对象的属性或者方法。
this关键字通常可以省略,但是如果方法的形参和类属性同名时,this不可省略。
this可以调用构造器,比如HashMap、String源码都有使用:
-
在类的构造器中,可以显式地使用"this(形参列表)"方式,调用本类中指定的其他构造器。
-
构造器中不能通过上述方法调用自己本身。
-
如果一个类中有n个构造器,则最多有n-1个构造器使用了上述调用方式。
-
规定"this(形参列表)"必须声明在首行,即构造器内部最多只能声明一个上述方式。
-
不能循环调用。
public Person(){
......
}
public Person(int age){
this();
this.age = age;
......
}
public Person(String name, int age){
this(age);
this.name = name;
......
}
package
为了更好地实现项目中类的管理,提供包的概念,即使用package生声明类或接口所属的包,声明在源文件的首行。
import
导入指定包下的类或者接口。声明在package和class之间。
如果源文件中使用了不同包下同名的类,则必须至少有一个类需要以全类名的方式显示。xx.xx.xx.XXclass。
MVC设计模式
视图模型层、控制器层、数据模型层。
模型层:model 主要处理数据。
控制层:controller 处理业务逻辑。
视图层:view 显示数据。
将程序输入输出、数据处理、以及数据的展示分离开来,使得程序结构灵活清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
继承
class A extends B {}
其中A:子类、派生类;B:父类、超类、基类。
一旦子类A继承父类B后,子类A中就获取了父类B中声明的所有属性和方法。特别地,父类中声明为私有的属性或方法,子类继承父类之后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
子类继承父类之后,还可以声明自己特有的属性或方法,实现功能的扩展。
java只支持单继承和多层继承,不允许多重继承。
子父类是相对的概念。直接继承的父类称为直接父类,间接继承的父类称为间接父类。一旦继承,子类可以获取所有父类(直接和间接)的所有属性和方法。
一个类可以被多个子类继承,一个类只有一个父类。
继承的好处:
-
减少代码冗余,提高代码的复用性。
-
便于功能的扩展。
-
为之后多态性的使用,提供了前提。
重写
子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
- 子类重写的方法必须和父类被重写方法具有相同的方法名称、参数列表。
- 子类重写的方法的返回值类型不能大于父类被重写方法,即
- 如果父类返回值类型是引用数据类型,则子类返回值类型必须是父类的返回值类型或是其返回指令类型的子类。
- 如果父类返回值类型是基本数据类型,则子类重写的方法返回值类型必须是相同的基本数据类型。
- 子类重写的方法使用的访问权限不能小于父类被重写方法。
- 子类不能重写父类中声明为private权限的方法。
- 子类重写方法抛出的异常不能大于父类被重写方法的异常。
注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写,static方法不能被重写)。因为static方法属于类,子类无法覆盖父类方法。
注意区分重载和重写 1.概念 2.具体规则 3.多态性
Object类
所有的类都直接或间接地继承于java.lang.Object类,如果在类中未使用extends显式指定,则默认继承于Object父类。
也就意味着所有java类都具有java.lang.Object类中声明的方法。
功能(方法):
- clone():创建并返回当前对象的一个复制。
- equals():重点,后续和“==”做对比。
- finalize():用作垃圾回收,通常JVM自动调用,不要主动调用。
- getclass():获取当前对象所属类。
- hashCode():获取当前对象的哈希值。
- notify():唤醒等待队列的一个线程。
- notifyAll():唤醒所有线程。
- toString():返回对象的字符串表示。
- wait():使线程进行等待队列。
super
super用来调用父类的方法、属性、构造器。例如super.属性、super.方法、super()、super(属性形参列表)。
在构造器首行,没有显式声明“this(形参列表)”或“super(形参列表)”,则默认调用父类的空参构造器。
在类的多个构造器中,至少有一个类的构造器调用了父类的构造器。即至少有一个构造器用“super()”,至多有n-1个构造器用“this()”。
当通过子类的构造器创建子类对象时,一定会直接或间接地调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑调用。(明确:虽然创建子类对象时,调用了一个或多个父类的构造器,但是自始至终只创建过一个对象,即为new的子类对象。)
多态
多态性可以理解为一个事物的多种形态。
对象的多态性:父类的引用指向子类的对象。(可以直接应用在抽象类和接口上)例如:Person p = new Man(); Person p = new WoMan();
使用前提:1.类的继承关系;2.方法的重写。
多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法(虚拟方法调用)。
多态情况下的虚拟方法调用:子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据付给他的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期无法确定。
编译时是父类类型,而方法的调用是在运行时确定的——动态绑定(晚绑定)。
重载问题中同个类的多个同名方法,对于编译器而言,是不同的方法,它们的调用地址在编译期就绑定了。——静态绑定(早绑定)
(重载包括父类和子类,子类可以重载父类的同名不同参数方法)
编译期调用父类中声明的方法,但运行期实际执行的是子类重写父类的方法。(编译看左边,运行看右边)
虚拟方法调用使用举例:
public class Day04 {
public static void main(String[] args) {
show(new Dog());//传入子类对象
show(new Cat());
}
public static void show(Animal animal){//形参是父类对象
animal.eat();
animal.shout();
}
//提高了代码的通用性
}
class Animal{
public void eat(){
System.out.println("动物吃");
}
public void shout(){
System.out.println("动物叫");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("吃骨头");
}
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("吃🐟");
}
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
2.数据库连接时的 java.sql.Connection父类,子类对应各类别数据库连接对象。
注意:对象的多态性只适用于方法,不适用于属性(属性编译运行都看左边)。
有个疑惑,多态在子类继承父类重写其方法的情况下实现,而里氏代换原则要求子类继承于抽象而不是可实例化的实体类。多态是否违背了里氏代换原则?参考下述知乎大佬的回答。
https://www.zhihu.com/question/27191817/answer/145013324
向下转型的使用(instanceof)
父类对象->子类对象:使用强制类型转换符转换为子类对象,然后调用子类特有的方法。(向下转型)
子类对象->父类对象:自动转换(多态)。
使用强制转换时,可能出现ClassCastException异常。为避免此问题,引入instanceof关键字,在进行强转前进行判断。
if(p2 instanceof Woman){
Woman w = (Woman) p2;
......
}
x instanceof A:检验x是否为类A的对象,返回值是布尔类型。
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x属于类A的子类B,x instanceof A的值也为true。
== 和 equals()
-
对于基本数据类型是比较值,对于引用数据类型是比较内存地址,且使用时要保证左右两边变量类型一致。
-
equals()属于java.lang.Object类中的方法,如果该方法没有重写过,则默认也是==;但是String、Date、File、包装类等类的equals()方法是被重写过的。
-
具体情况要判断自定义类中有没有重写Object类的equals()方法来判断。
-
通常情况下,重写equals()方法,会比较类中的相应属性是否都相等。
重写equals()举例(IDE常常可以自动生成):
class Animal{
String name;
int age;
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(obj instanceof Animal){
Animal animal = (Animal)obj;
return this.age == animal.age && this.name.equals(((Animal) obj).name);
}
return false;
}
}
重写原则:对称性、自反性、传递性、一致性。
toString()
输出一个对象的引用时,实际上就是调用了该对象的toString()方法。
String、Date、File、包装类等都重写了toString()方法,使得在调用其对象的toString()方法时,返回其“实体内容”信息。
故自定义类有类似需求时,需要重写toString()方法。(IDE可以自动生成)
单元测试
Junit单元测试。@Test注解。
包装类(装箱、拆箱)
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
其中数值型数据类型(前6种)的包装类的父类是Number类。
基本数据类型、String、包装类之间的转化:
JDK5之后已经可以实现自动拆箱和自动装箱,相关方法可以不再使用。
有个坑:
Integer i = 1;
Integer j = 1;
System.out.println(i == j); //true
因为Integer内部定义了IntegerCache结构,该结构中定义了Integer[]数组,保存了从-128-127范围内的整数。如果使用自动装箱的方式,给Integer赋值的范围在-128-127范围内,则会直接使用数组中的元素,不再new。
数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需求动态伸缩。
Vector<Object> v = new Vector<Object>();
v.addElement(Object o); //添加元素
Object o = v.elementAt(int index);//取索引为index的元素
v.size();//向量大小
v.remove(Object o);//按对象删除元素
v.remove(int index);//按索引删除元素
static
某种情况下,希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份。
static可以用来修饰属性、方法、代码块、内部类。(构造器不能用static修饰)
使用static修饰的变量——静态变量(类变量):
- 属性按照是否使用static修饰,又分为静态属性和非静态属性(实例变量)。其中的实例变量——当创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性,修改其中一个对象的非静态属性时,不会导致其他对象中的同样属性值的修改。
- 多个对象共享一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时是已修改的值。
- 静态变量的加载随着类的加载而加载。可以通过“类.静态变量”的方式进行调用。
- 静态变量的加载早于对象的加载。
- 由于类只会加载一次,则静态变量在内存中也只会存在一份,存在方法区的静态域中。
使用static修饰的方法——静态方法:
- 静态方法中只能调用静态方法或静态变量。非静态方法都可。
- 静态方法中,不能使用this、super关键字。
开发中,如何确定一个属性或一个方法是否要声明为static?
- 可以被多个对象共享,不会随着对象不同而不同的属性设置为static。
- 操作静态属性的方法,通常也设置成static。
- 工具类中的方法,习惯上声明为static,例如Math、Arrays、Collections等。
- final修饰的常量也常常声明为static。
String不可变性
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final byte[] value;
private final byte coder;
}
被声明为final,所以String不可变,不可被继承(Integer等包装类也不可被继承)。内部使用 byte 数组存储字符串。
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
不可变的好处:
1. 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
2. String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //true
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String、StringBuffer、StringBuilder
-
可变性
- String 不可变。
- StringBuffer 和 StringBuilder 可变。
-
线程安全
-
String 不可变,因此是线程安全的。
-
StringBuilder 不是线程安全的。
-
StringBuffer 是线程安全的,内部使用 synchronized 进行同步。
-