1. 继承
1.1继承的概念
继承是 java 面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
1.2类的继承格式
class 父类 {
}
class 子类 extends 父类 {
}
公共父类:
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
这个Animal类就可以作为一个父类,然后企鹅类和老鼠类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码) 继承之后的代码:
企鹅类:
public class Penguin extends Animal {
public Penguin(String myName, int myid) {
super(myName, myid); //super 调用父类构造方法
}
}
老鼠类:
public class Mouse extends Animal {
public Mouse(String myName, int myid) {
super(myName, myid); //super 调用父类构造方法,并向其传参数
}
}
1.3继承类型
注意:需要注意的是 Java 不支持多继承,但支持多重继承。
1.4继承的特性
-
子类拥有父类非 private 的属性、方法。
-
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
-
子类可以用自己的方式实现父类的方法。
-
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
-
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
1.5 继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
public class Animal {
private String name;
private int id;
public Animal(String myName, String myid) {
//初始化属性值
}
public void eat() { //吃东西方法的具体实现 }
public void sleep() { //睡觉方法的具体实现 }
}
public class Penguin extends Animal{
}
implements关键字
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
super 与 this 关键字
super关键字
:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字
:指向自己的引用。
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}
final关键字
final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:
声明类:
final class 类名 {//类体}
声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注:
实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final
2. Object:所有类的超类
2.1 概念
Obje类是所有Java类的祖先。
每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。
在不明确给出超类的情况下,Java会自动把Object作为要定义类的超类。
可以使用类型为Object的变量指向任意类型的对象。
Object类是Java中唯一没有父类的类
Object类有一个默认构造方法pubilc Object(),在构造子类实例时,都会先调用这个默认构造方法。
2.2 Object中几个重要的方法:
equals() (判断两个对象是否相等,用于比较两个对象的内存地址)
1)基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean 他们之间的比较,用双等号(==),比较的是他们的值。
2)复合数据类型(类) 当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
hashCode()( 返回该对象的哈希码值)
hashCode 的常规协定是(重写equals()时,必须重写hashcode()并保证此协定):
如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等
如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false
如果两个对象的hashcode值相等,则equals方法得到的结果未知
-
toString()(返回该对象的字符串表示)
Object类中的toString()方法会打印出类名和对象的内存位置。几乎每个类都会覆盖该方法,以便打印对该对象当前状态的表示。大多数(非全部)toString()方法都遵循如下格式:类名[字段名=值,字段名=值...],当然,子类应该定义自己的toString()方法。该方法是非常重要的调试工具,很多标准类库中的类都定义了toString()方法,以便程序员获得有用的调试信息
public static void main(String[] args) {
Main m = new Main();
System.out.println(m.toString()); //complete.Main@7852e922
System.out.println(m); //complete.Main@7852e922
}
注意: java 是开源,源代码公开,,,
查看源代码方式:
方式一:按住 Ctrl 键,单机你需要看的源代码。
方式二:把光标移动到你需要查看代码处,按下 F3
为什么要查看源代码?
1.查看源代码可以了解别人是如何写出这个技术的,让我们了解的更加的深入。
2.吸收大牛思想。
toString() 方法有何作用?
重写 toString() 之后,我们直接输出一个对象的时候,就会输出符合我们所需求的格式数据
class person {
String name;
int age;
public person(String name, int age) {
this.name = name;
this.age = age;
}
//重写 toString() 方法
public String toString() {
return "姓名:" + this.name + " 年龄:" + this.age;
}
}
public class Main {
public static void main(String[] args) {
person p = new person("haha", 18);
System.out.println(p);
}
}
结果:
3. 泛型
3.1 泛型定义:
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
例如:
需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?
答案:是可以使用 Java 泛型。
3.2 泛型方法
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
public class Main {
// 泛型方法 printArray
public static <E> void printArray(E[] arry) {
// 输出数组元素
for (int i = 0; i < arry.length; i++) {
System.out.printf("%s ", arry[i]);
}
System.out.println();
}
public static void main(String args[]) {
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println("整型数组元素为:");
printArray(intArray); // 传递一个整型数组
System.out.println("\n双精度型数组元素为:");
printArray(doubleArray); // 传递一个双精度型数组
System.out.println("\n字符型数组元素为:");
printArray(charArray); // 传递一个字符型数组
}
}
结果:
3.3 泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
举例:
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("菜鸟教程"));
System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}
结果:
image.png
4. 对象包装器与自动打包
4.1 对象包装器
所有基本类型都有一个与之对应的类,例如,Integer类对应基本类型int。通常,这些类称为包装器(wrapping)
。
这些对象包装器拥有很鲜明的名字:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前面六个类派生于公共的超类Number)。对象包装器类是不可变的,一旦构造了包装器,就不允许更改包装器里面的值。
同时,对象包装器还是final,因此不能定义他们的子类。
4.2 自动打包
定义一个整形数组列表,尖括号里面的参数类型不允许是基本类型,也就是说不允许写成ArrayList<int>。这里就用到了Integer对象包装器类。我们可以声明一个Integer对象的数组列表。
ArrayList<Integer> list = new ArrayList<Integer>;
当调用:
list.add(3);
将自动变换成:
list.add(new Integer(3));
这种变换称之自动打包(autoboxing)
。
相反的如果将Integer对象赋值给一个int值时,将会 自动第拆包
,也就是说编译器将下列语句:
int n = list.get(i);
翻译成:
int n = list.get(i).intValue();
甚至在算术表达式中也能够自动的打包和拆包。例如,可以将自增操作符应用于一个包装器引用:
Integer n = 3;
n++;
编译器将自动第插入一条拆开对象包的指令,然后进行自增运算,最后再将结果打入对象包内。
很多情况下,容易有一种假象,即基本类型和他们的对象包装类是一样的,只是他们的相等性不同,==
运算符也可以用于包装器对象
,只不过检测的是对象是否指向同一个存储区域。
先看下面一段代码:
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
输出结果为:true,false。结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。下面这段代码是Integer的valueOf方法的具体实现:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
自动打包要求规范要求boolean、byte、char<=127,介于-128~127之间的short和int被包装到固定的对象中,否则创建一个新的对象
。如上面的i1和i2初始化为100,对他们的比较的结果一定是成立的。
所以在比较两个包装器对象的值是否相等时调用equals方法就能避免出现false的情况。
最后说明一下,打包和拆包是编译器认可的,而不是虚拟机。编译器在生成类的字节码时插入必要的方法调用。虚拟机只是执行这些字节码。
5.参数数量可变的方法
可变的参数类型
,也称为不定参数类型
。英文缩写是varargus,还原一下就是variable argument type。通过它的名字可以很直接地看出来,这个方法在接收参数的时候,个数是不定的。
Java开始支持可变参数的方法,比如printf方法就是一个可变参数的方法。
我们也可以自定义可变参数的方法,具体语法为:
返回类型 函数名(参数类型...参数名字)
原理:就是将接收的参数组装成一个临时数组,然后再处理临时数组中的数据。
示例代码:
public class Main {
public static double max(double... values) { // //定义可变参数 values
double largest = Double.MIN_VALUE; ////次数为 Double 能表示的最小值
for (double v : values) { // 相当于把values数组里的每一个值取出赋于v,直到结束
largest = v > largest ? v : largest; // 取大值
}
return largest;
}
public static void main(String[] args) {
double m = max(1.2, 3.4, 56.7, 345.45, 2, 5);
System.out.println(m);
}
}
结果:
image.png
这段代码接受任意个double类型的参数,从中寻找其中的最大值并在控制台输出。
要点:
-
在方法中定义可变参数后,我们可以像操作数组一样操作该参数;
-
如果该方法除了可变参数还有其它的参数,可变参数必须放到最后;
-
调用使用了可变参数的方法时:
a. 可以不写参数,即传入空参;
b. 可以直接在里边写入参数,参数间用逗号隔开;
c. 可以传入一个数组;
6.枚举类
6.1 枚举的定义
这是在没有枚举类型时定义常量常见的方式
//使用普通方式定义日期常量
public class DayDemo {
public static final int MONDAY =1;
public static final int TUESDAY=2;
public static final int WEDNESDAY=3;
public static final int THURSDAY=4;
public static final int FRIDAY=5;
public static final int SATURDAY=6;
public static final int SUNDAY=7;
}
上述的常量定义常量的方式称为int枚举模式,这样的定义方式并没有什么错,但它存在许多不足,如在类型安全和使用方便性上并没有多少好处,如果存在定义int值相同的变量,混淆的几率还是很大的,编译器也不会提出任何警告,因此这种方式在枚举出现后并不提倡,现在我们利用枚举类型来重新定义上述的常量,同时也感受一把枚举定义的方式,如下定义周一到周日的常量
//枚举类型,使用关键字enum
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
相当简洁,在定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型。
枚举类型Day中分别定义了从周一到周日的值,这里要注意,值一般是大写的字母,多个值之间以逗号分隔
。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性
和便捷性
都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的,比如上述描述的一周共有七天。那么该如何使用呢?如下:
public class EnumDemo {
public static void main(String[] args){
//直接引用
Day day =Day.MONDAY;
}
}
//定义枚举类型
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
就像上述代码那样,直接引用枚举的值即可,这便是枚举类型的最简单模型。
6.2 枚举类的定义
枚举类和普通类有以下几个不同点:
1、枚举类不能指定继承的父类(因为继承了java.lang.Enum
类),但是可以实现多个接口, 枚举类默认实现了Comparable
接口和Serializable
接口
2、枚举类的构造方法的访问权限只可为private
3、枚举类的实例必须显式列出。
6.3 枚举类的真正实现
举例:
public enum Season {
SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
private final String chinese;
private Season(String chinese) {
this.chinese = chinese;
}
public String getChinese() {
return chinese;
}
}
其实上述代码相当于:
public final class Season extends Enum<Season> {
public static final Season SPRING;
public static final Season SUMMER;
public static final Season AUTUMN;
public static final Season WINTER;
private static final Season[] ENUM$VALUES;
static {
SPRING = new Season("SPRING", 0, "春天");
SUMMER = new Season("SUMMER", 1, "夏天");
AUTUMN = new Season("AUTUMN", 2, "秋天");
WINTER = new Season("WINTER", 3, "冬天");
ENUM$VALUES = new Season[]{SPRING, SUMMER, AUTUMN, WINTER}
}
private final String chinese;
private Season(String name, int ordinal, String chinese) {
super(name, ordinal);
this.chinese = chinese;
}
public String getChinese() {
return chinese;
}
public static Season[] values() {
Season[] arr = new Session[ENUM$VALUES.length];
System.arraycopy(ENUM$VALUES, 0, arr, 0, arr.length);
return arr;
}
public static Season valueOf(String name) {
return Enum.valueOf(Season.class, name);
}
}
在编译过程中,编译器会对枚举类进行以下处理:
1、增加一个静态初始化块和一个静态数组,静态初始化块负责构造枚举类的实例并将其保存到数组中。
2、修改当前枚举类的构造方法,向构造方法前面增加两个参数:name, ordinal。分别代表枚举类的名称和初始化顺序,并且使用super调用父类java.lang.Enum的构造方法。
3、添加两个静态方法:
public static Season[] values()
该方法返回一个数组,包含了该枚举类所有的实例(并不是返回枚举类自己保存实例的数组,而是通过对其复制,以确保实例不被篡改)。
public static Season valueOf(String name)
该方法通过当前枚举类的名称返回对应的实例。比如valueOf(“SPRING”)返回Season.SPRING)。
7.反射
7.1 反射的概念
JAVA反射机制
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
网友评论