JAVA学习记录

记录学习JAVA过程中易意忘的小知识点。

字符类型

1
2
3
char word = 'd';
int(word); //64
char word = 64; <==> char word= 'd';

变量

类体中所定义的变量为成员变量。类的成员变量分为静态变量和实例变量。类的方法中定义的为局部变量。

成员变量在设定时需要设置初始值,局部变量在使用时必须进行赋值操作或者被初始化。

静态变量

被声明为static的变量、常量和方法被称为静态成员。静态成员属于类所有,可以在本类或者其他类使用类名和”.”运算符调用静态变量。静态成员同样遵循public、private和protected修饰符的约束。

静态方法中不能使用this关键字。方法内的局部变量不能声明为静态变量。

1
2
3
4
5
public class example{
static {
//some
}
}

栈堆

当运行int x = 3时,会在栈内存寻找一块区域存放3.

栈内存:数据使用完毕,会自动释放

当这段代码被执行时,首先执行static块中的程序。

当运行int[] x = new int[3],栈内存存放一块区域存放数组x的首地址。而数组的元素存放在堆中。此时执行x = null,则x与堆中的元素没有关系。此时数组在堆内存并没有消失,而是会有java不定时执行垃圾回收机制。

堆内存用来封装数据,当被初始化时,均使用默认值。int型数组的默认值为0,double数组为0.0,float数组为0.0f。

1
2
3
4
5
int[] x = new int[3];
int[] y = x;//两个引用指向同一个数组
y[1] = 89;
x = null;
//此时堆中不存在垃圾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AccessProperty{
static int i =47;
public void call(){
System.out.println("调用call()方法");
for(i=0;i<3;i++){ //第二次改变i的值
System.out.println(i + " ");
if(i==2){
System.out.println("/n");
}
}
}
public AccessProperty(){

}
public static void main(String[] args){
AccessProperty t1 = new AccessProperty();
AccessProperty t2 = new AccessProperty();
t2.i=60; //第一次改变i的值
System.out.println(t1.i); //60
t1.call();
System.out.println(t2.i); //3
}
}
//如果将第五行改为for(int i=0;i<3;i++)
//则两次输出均为60

运算符优先级

增量和减量运算>算术运算>比较运算>逻辑运算>赋值运算

逻辑运算符

&& 短路运算(当第一个表达式为false,则不判定第二个表达式)

&非短路运算(第一个和第二个都会进行判断)

位运算符

左移(<<) 左移空的补0;

右移(>>)右移部位和原始最高为一致;

无符号右移(>>>)右移最高为填0;

foreach

1
2
3
4
5
6
7
8
for(元素变量x:遍历对象 obj){
引用x的java语句;
}
eg:
int arr[] = {1,2,3};
for(int x:arr){
System.println(x);//将数组arr的元素逐个输出
}

字符串

java中将字符串作为对象来管理,因此可以像创建其他类对象一样来创建字符串对象。创建对象要使用类的构造方法。

创建

String(char a[])
1
2
3
char a[] = {'g','o','o','d'};
String s = new String(a);
等价于 String s = new String("good")
String(char a[], int offset, int length)
1
2
3
char a[] = {'s','t','u','d','e','n','t'};
String s = new String(a,2,4);
等价于 String s = new String("uden")

查找

indexOf(String s)
1
2
String str = 'We are students';
int size = str.indexOf("a"); //size=3 查找字符a在字符串str中的索引位置,未找到返回-1
lastIndexOf(String str)

返回字符串最后一次出现的索引位置。如果没有检索到字符串str,该方法返回-1

PS:lastIndexOf()方法中的参数是空字符串,则返回结果与调用该字符串length()方法的返回结果相同。

str.CharAt(int index)

将索引处的字符返回。

获取子字符串

substring()

str.substring(int beginIndex) 返回从索引位置开始截取直到该字符串结尾的字串

str.substring(int beginIndex, int endIndex)返回从字符串某一索引位置开始截取至某一索引位置结束的字串。

替换

replace(char oldChar, char newChar)

返回替换后的字符串

验证

startsWith(String prefix)

判断当前字符串对象的前缀是否是参数

endsWith(String suffix)

判断当前字符串对象是否以给定的子字符串结束

equals(String otherstr)

比较两个字符串的内容,区分大小写。

对字符串对象进行比较不能简单地使用”==“,比较运算符比较的是两个字符串的地址是否相同。

equalsIgnoreCase()

比较两个字符串的内容,不区分大小写。

compareTo()

按字典顺序比较两个字符串,比较基于字符串中各个字符的Unicode值,如果此String对象位于参数字符串之前,则返回一个负整数;如果在其之后,则返回一个正整数;如果这两个字符串相等,则结果为0。

大小写转换

toLowerCase() 转换成大写
toUpperCase() 转换成小写

分割

split(String sign)

sign为分隔符,多个分隔符之间用|来连接。eg: “,|=” 表示分隔符分别为“,”和“=”

可以使用正则表达式

split(String sign, int limit)

limit: 限制的分割份数。

PS: | , + , * , ^ , $ , / , | , [ , ] , ( , ) , - , . , \。例如用|竖线去分割某字符,因|本身是正则表达式中的一部分,所以需要\去转义,因转义使用\,而这\正好也是正则表达式的字符,所以还得用一个\,所以需要两个\。

字符串生成器

StringBuilder对象初始容量为16个字符,可以自行指定初始长度。最后输出StringBuilder的字符串结果,可以使用toString()方法。

append(content)

用于向字符串生成器中追加内容。可以接受任何类型的数据,如int、boolean、char、String、double

insert(int offset,arg)

offset:字符串生成器的位置。 arg:新插入的字符串

delete(int start, int end)

删除从start下标到end下标位置处的字符串。

1
2
3
StringBuilder bf = new StringBuilder("hello");
bf.insert(5,"world");
System.out.println(bf.toString()); =>输出为helloworld

数组

数组的初始化

1
2
3
int arr[] = new int[]{1,2,3,4,5};      //第一种初始化方式
int arr2[] = {34,23,12,6}; //第二种初始化方式
int[] arr = new int[2];相当于 int arr[] = new int[2];

数组排序

选择排序法

依次找到数组的最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
class ArrayTest2{
public static void selectSort(int arr[]){
for(int x=0;x<arr.length-1;x++){
for(int y=x+1 ;y<arr.length;y++){
if(arr[x]>arr[y]){
int temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
}
}
}
}
冒泡排序

相邻的两个元素进行比较,如果符合条件换位。

相当于每次找到数组的最大值放在后边

1
2
3
4
5
6
7
8
9
10
11
12
13
class ArrayTest2{
public static void bubbleSort(int arr[]){
for(int x=0;x<arr.length-1;x++){
for(int y=0 ;y<arr.length-x-1;y++){
if(arr[y]>arr[y+1]){
int temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
}
}
}
}

PS:上述排序效率比较低,其在堆内存中排序。如果将排序过程需要置换下标的数据放入栈内存,最后使用堆内存统一排序。则可以提高不少的效率。

置换方法
1
2
3
4
5
swap(int[] arr,int a,int b){ //传入一个数组和需要排序的下标
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}

PS:将数组传入函数,相当于在栈中存在一个临时数组指向堆中的数组。这样函数操作的数组和实际参数的数组保持一致。

实际开发
1
2
3
import java.util.*;
int[] arr = {5,4,3,2,1};
Array.sort(arr);

数组的查找

折半查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static int halfSearch(int[] arr,int key){
int min = 0;
int max = arr.length-1;
int mid = (min+max)/2;
while(arr[mid]!=key){
if(arr[mid]<key){
min = mid + 1;
}else if(arr[mid]>key){
max = mid - 1;
}
mid = (min+max)/2;
if(min>max){
return -1;
}
}
return mid;
}

第二种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
public static int halfSearch_2(int[] arr,int key){
int min = 0,max = arr.length,mid;
while(min<=max){
mid = (min+max)>>1;
if(key > arr[mid]){
min = mid + 1;
}else if(key < arr[mid]){
max = mid-1;
}
return mid;
}
return -1; //retrun min 就是插入有序数组
}

二维数组

初始化
1
2
3
int[][] arr = new int[3][4];
System.out.println(arr); //[[i@地址
System.out.println(arr[0]); //[i@地址
1
2
3
4
5
6
7
int[][] arr = new int[3][];
//此时arr[0] = null;arr[1] = null;arr[2] = null;
arr[0] = new arr[2];
arr[1] = new arr[3];
arr[2] = new arr[1];
Syetem.out.println(arr.length); // 3
System.out.println(arr[1].length);//2
二维数组的遍历
1
2
3
4
5
6
7
8
9
10
public class Tautog{
public static void main(String[] args){
int arr2[][] = {{4,3},{1,2}};
for(int x[]:arr2){
for(int e:x){
System.out.println(e);
}
}
}
}

填充

fill(int[] a, int value)

该方法可将指定的int值分配给int型数组的每个元素。

fill(int []a, int fromIndex, int toIndex, int value)

将int值分配给从索引fromIndex(包括)到toIndex(不包括)范围内的每个元素

排序

Arrays.sort(object);

object是进行排序的数组名称。升序排列

复制

copyOf(arr, int newlength)

arr:要进行复制的数组。

newlength: int型常量,指复制后的新数组的长度。如果新数组的长度大于数组arr的长度,则用0填充(char型数组用null填充);复制后的数组长度小于数组arr的长度,则会从数组arr的的第一个元素开始截取至满意新数组长度为止。

1
int newarr[] = Arrays.copyOf(arr,5)
copyOfRange(arr, int formIndex, int toIndex)

复制给从索引fromIndex(包括)到toIndex(不包括)范围内的每个元素

查询

binarySearch(Object[].Object key)

使用二分法搜索法来搜索指定数组。使用前必须将数组排序。如果检索到指定数据,则返回排序后数组的下标。如果未检索到数据,则返回检索数据在数组中排列的位置+1取反。

详细源码:https://blog.csdn.net/qq_40178464/article/details/79942814

binarySearch(Object[], int fromIndex, int toIndex, Object key)

在指定的范围内检查某一元素。指定范围应小于数组长度。

进制转换

十进制转二进制

1
2
3
4
5
6
7
8
public static void toBin(int num){
StringBuffer sb = new StringBuffer();
while(num > 0){
sb.append(num%2);
num = num/2;
}
System.out.println(sb.reverse());
}

十进制转十六进制

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void toHex(int num){ //计算的方式
StringBuffer sb = new StringBuffer();
for(int x = 0;x<8;x++){
int temp = num & 15;
if(temp>9){
sb.append((char)(temp-10+'A'));
}else{
sb.append(temp);
}
num = num >>>4;
}
System.out.println(sb.reverse());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void toHex(int num){   //数组的方式
char[] chs = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] res = new char[8];
int pos = arr.length;
while(num!=0){
int temp = num &15;
arr[--pos] = chs[temp];
num = num >>> 4;
}
System.out.println("pos=" + pos);
for(int x = pos;x<arr.length;x++){
System.out.println(arr[x]+",")
}
}

综合进制转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static void main(String[] args){
toBin(6);
toBa(10);
toHex(60);
}
public static void toBin(int num){
tran(num,1,1);
}
public static void toBa(int num){
tran(num,7,3);
}
public static void toHex(){
tran(num,15,4)
}


public static void tran(int num,int base,int offset){
//base为与运算的数值,offset为左移位数
if(num == 0){
System.out.println(num);
return;
}
char[] chs = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] res = new char[32];
int pos = res.length;
while(num!=0){
int temp = num & base;
res[--pos] = chs[temp];
num = num >>>offset;
}
for(int x = pos; x<arr.length;x++){
System.out.println(arr[x])
}
}

类与对象

面向对象的三个特征:封装、继承、多态

变量

成员变量

成员变量在堆内存中,因为对象的存在,才在内存中存在。

局部变量在栈内存中。

对象的比较
1
2
3
4
5
6
7
8
9
public class Compare{
public static void main(String[] args){
String c1 = new String("abc");
String c2 = new String("abc");
String c3 = c1;
}
}
// "==" ==运算符比较的是两个对象引用的地址是否相等,c1==c3,c1≠c2;
// equal 比较连个对象引用所指的内容是否相等 c1.equals(c2) 为true

类修饰符

如果一个类使用protected修饰符,那么只有本包内的该类的子类或者其他类可以访问此类中的成员变量和方法。默认的修饰符为protected。

在类中没有定义任何构造方法时,编译器才会在该类中自动创建一个不带参数的构造方法。

private Tool(){}:当Tool类的构造函数人为定义私有化时,该类不能创建对象(见单例设计模式)。一般用于工具类,其类里边对外提供的方法均为静态方法。 默认构造函数的修饰符随着类修饰符的变化而变化。

匿名对象

1
2
3
new Car().num = 5;   //匿名对象
new Car().color = “red”;
new Car().run();

图一:

对象创建

匿名对象使用方式:当对象的方法只调用一次时,可以用匿名对象来完成;可以将匿名对象作为实际参数进行传递。

1
2
3
4
5
6
7
8
//Car q = new Car();
//show(q);
show(new Car()); //匿名对象作为实际参数进行传递
public static void show(Car c){
c.num = 3;
c.color = "black";
c.run();
}

构造代码块

1
2
3
4
5
6
7
8
9
10
11
class Person{
private String name;
private int age;
{
System.out.println("Person code run");
//构造代码块。作用:给对象初始化。
//对象一建立就运行,而且优先于构造函数执行
//区别:构造代码块是给所有对象进行统一初始化,
//而构造函数是给对应的函数初始化
}
}

this在构造函数之间的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person{
private String name;
private int age;
Person(String name){
this.name=name;
}
Person(String name,int age){
this(name); //p(name);调用其他的构造函数,且只能放在构造函数的第一行。
this.age=age;
}
}
class mainfun{
public static void main(String[] ages){
Person p = new person("list",30);
}
}

Static关键字

当所有的对象某一个属性值都是一样的时候,则可以使用static来修饰类中的成员变量。这样所有对象的这个属性公用一个空间,静态的成员变量也称之为类变量

调用方式:对象.属性 或者 类名.属性

存放位置:静态成员变量随着类的加载而存在与方法区(共享区),实例变量随着对象的建立而存在堆内存中。

生命周期:静态成员变量周期最长。

注意事项:

1.静态方法只能访问静态成员和静态方法;非静态方法既可以访问静态也可以访问非静态。

2.静态方法中不可以定义this,super关键字,因为静态优先于对象存在。

静态特点:

1.静态成员变量随着类的加载而加载,随着类的消失而消失。而普通的成员变量随着对象的存在而存在;

2.对象的共享数据进行单独空间的存储,节省空间。

3.可以被所有对象所共享,可以直接被类名调用。

静态的使用

静态成员变量

当对象中出现共享数据时,该数据被静态所修饰。

对象中的特有数据要定义成静态存在于堆内存中。

静态函数

当函数内部没有访问到非静态数据,那么该函数可以定义为静态的。

主函数

主函数是一个特殊的函数,可以被jvm调用,作为程序的入口。

public:代表着该函数的访问权限是最大的。

static:代表主函数随着类的加载就已经存在了。

void:主函数没有具体的返回值。

main:不是关键字,是一个特殊的单词,可以被jvm识别。

String[] args:字符串类型的数组。只有args可以变。

jvm在调用主函数时,传入的是new String[0];

主函数的传参

静态代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class StaticCode
{
static{
System.out.println("a");
}
}
class StaticCodeDome
{
static{
System.out.println("b")
}
public static void main(String[] args){
new StaticCode();
new StaticCode();
System.out.println("over");
}
static{
System.out.println("c")
}
}

上述例子输出结果为 b c a over。静态代码块随着类的加载而执行,只执行一次,并优先于主函数,用于给类进行初始化。PS

PS:StaticCode s =null此时类没有被加载。

对象的初始化过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person{
private String name;
private int age;
private static String country = "cn";
Person(String name,int age){
this.name=name;
this.age=age;
}
{
System.out.println(name + "."+age);
}
public void setName(String name){
this.name=name;
}
public void speak(){
System.out.println(this.name + ".."+this.age);
}
public static void showCountry(){
System.out.println("country="+country);
}
}

class Dome{
public static void main(String[] args) {
Person p = new Person("zhangsan",20);
p.setName("lisi");
}
}

Person p = new Person()的执行顺序:

1
2
3
4
5
6
7
8
9
main函数进入栈中;
Person p = new Person();
new 用到了Person.class,所以先找到Person.class并加载到内存中。
在堆内存中开辟空间,分配内存地址;
建立对象的静态属性,并执行静态代码块;
对象属性初始化name=null age=0
构造代码块初始化
构造函数初始化 name=zhangsan age=20
将内存地址赋值给栈内存中的p变量。

p.setName("lisi") 的执行顺序:

1
2
3
4
setName方法进入栈内存
this和name进入栈内存;this的值为p的值,name的值为“lisi”;
将堆内存中name的值改为lisi;
setName方法从栈内存释放

p3_对象初始化过程

设计模式

单例设计模式

方法一 饿汉式

可以保证系统中 ,应用该模式的类只有一个实例。

方式:1.将构造函数私有化;2.在类中创建一个本类对象;3.提供一个方法可以获取到该对象。

1
2
3
4
5
6
7
8
9
10
11
12
class Single{
private Single(){}
private static Single s = new Single();
public static Single getInstance(){
return s;
}
}
class SingleDemo{
public static void main(String[] args){
Single s = Single.getInstance();
}
}

p4_单例设计模式

方法二 延时加载(懒汉式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s==null)
s = new Single();
return s;
}
}
class SingleDemo{
public static void main(String[] args){
Single s = Single.getInstance();
}
}

优点:当类SingleDemo被加载时,静态变量的s未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化s变量,并分配内存,因此在某些特定条件下会节约内存。

缺点:在多线程环境中,这种实现方法是错误的,根本不能保证单例的状态。

改进:public static Single getInstance()=>public static synchronized Single getInstance()线程锁的存在使程序效率低下。

改改进:

1
2
3
4
5
6
7
if(s == null){
syschronized(Single.class){ //减少判断锁的次数
if(s == null)
s = new Single();
}
return s;
}

开发一般用饿汉式

继承

继承提高了代码的复用性;让类与类之间产生了关系。有了这个关系,才有了多态的特性。extends不要为了获取其他类的功能,简化代码而继承;必须是类与类之间有所属关系才可以继承。

注意:Java只支持单继承,不支持多继承。 因为多继承容易带来安全隐患。但是支持多层继承。

查阅父类功能,创建子类对象使用功能。

子类特点

类中成员:变量、函数、构造函数。

变量

当父类和子类出现非私有的同名成员变量时,优先使用子类变量。子类要访问本类的变量,用this。子类要访问父类的变量,用super。

this是本类的引用,super是父类的引用。

函数

当父类和子类出现非私有的同名函数(返回值也需要一致)时,当子类对象调用该函数时,会运行子函数的内容。即重写(覆盖)。

1.子类覆盖父类,必须保证子类权限大于等于父类权限(private不能覆盖public),才可以覆盖,否则编译失败。2.静态只能覆盖静态。

构造函数

在对子类对象进行初始化时,父类的构造函数也会执行,因为子类的构造函数会默认第一行有一条隐式的语句 super();如果要访问父类中指定的构造函数,可以通过手动定义super语句来指定。

PS:子类的构造函数第一行也可以通过手动指定this语句来访问本类中的其他构造函数。因为子类中至少有一个构造函数会访问父类中的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Fu{
Fu(){
System.out.println("fu run");
}
Fu(int y){
System.out.println("fu"+y);
}
}
class Zi extends Fu{
Zi(){
System.out.println("zi run");
}
Zi(int x){
System.out.println("zi"+x);
}
}
class Demo{
public static void main(String[] args){
Zi z = new Zi();//fu run zi run
Zi z1 = new Zi(4);//fu run zi4
}
}
//fu run
//zi run

final

可以修饰类、函数、变量;被final修饰的类和函数不能被继承;

被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,又可以修饰局部变量。常量的书写规范所有字母大写。

抽象类

多个类出现相同函数名,但是函数主体不同时,可以只抽取函数名称,而不抽取功能主体。

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Student{
abstract void study();//没有执行体 没有大括号
}
class BaseStudent extends Student{
void study(){
System.out.println("base study");
}
}
class AdvStudent extends Student{
void study(){
System.out.println("AdvStduent")
}
}
抽象类特点

抽象方法一定在抽象类中;抽象类可以有非抽象方法;抽象类不可能用new创建对象;如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

PS:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。

接口

初期理解:可以认为是一个特殊的抽象类。要求其类中的方法都是抽象的。用interface来定义。

interface的对变量的修饰符为:public static final;对函数的修饰符为public abstract。均可以省略。

1
2
3
4
5
interface Inter
{
public static final int NUM = 3;
public abstract void show();
}

因为接口中均为抽象方法,因此不可以创建对象。需要被子类实现,将接口中的抽象方法全部覆盖后,子类才能实例化。

这时,子类的修饰符不用比接口类大。

1
2
3
4
5
6
7
8
9
10
11
interface Inter 
{
public static final int NUM = 3;
public abstract void show();
}
class Test implements Inter
{
void show(){
System.out.println("show");
}
}

接口可以进行多实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface InterA
{
public static final int NUM = 3;
public abstract void show();
}
interface InterB
{
public static final int NUM = 3;
public abstract void say();
}
class Test implements InterA,InterB{ //多实现
public void show(){}
public void say(){}
}
class Fu{
public void function(){}
}
class Zi extends Fu implements InterA,InterB{ //单继承多实现同时存在

}

接口与接口之间也是继承关系,但是接口和接口之间支持多继承

接口的使用

抽象类是核心属性或者功能,而接口是扩展属性或者功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Student
{
abstract void study();
void sleep(){
System.out.println("sleep");
}
}
interface Smoking
{
void smoke();
}
class zhangsan extends Studnet implements Smoking//张三的核心属性是学习,抽烟是扩展属性。如果不抽烟可以不加smoking
{
void stduy(){}
public void smoke(){}
}

多态

事物存在的多种体现形态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("啃骨头");
}
public void lookHouse(){
System.out.println("看家");
}
}
class Demo{
public static void main(String[] args){
/*
Cat c = new Cat();
function(c);
Dog d = new Dog();
function(d);
*/
Animal d = new Dog();//向上转型
function(new Cat());

}
public static void function(Animal a) {
a.eat();//吃鱼
}
/*
public static void function(Cat c) {
c.eat();
}
public static void function(Dog d) {
d.eat();
}
*/
}

体现:

父类的引用指向了自己的子类对象;父类引用可以接收自己的子类对象。

1
2
3
4
5
Animal c = new Cat();
...
public static void function(Animal a){
a.eat();
}

前提:1.类与类之间有关系(继承、实现);2.存在覆盖。

特点:提高代码的复用性,但是只能使用父类的引用访问父类中的成员

由于Animal a =new Cat();是向上转型。即将a转化为animal,则不能使用Cat类中的特有属性。因此需要强制转换

1
2
3
4
Animal a = new Cat();//向上转型
a.eat();
Cat c = (Cat)a;//强制转换
c.catchMouse();

上述例子的function方法可以单独封装为一个类:

1
2
3
4
5
6
7
8
9
10
class Doanimal{		//执行类
public void doeat(Animal a){
a.eat();
}
}
...
mian{
Doanimal da = new Doanimal();
da.doeat(new Cat());
}

特点:

对于非静态成员函数而言 编译时期:参阅引用型变量所属的类中是否有调用的方法。运行时期:参阅对象所属的类中是否有调用的方法。

对于变量和静态成员而言 编译和运行都参考引用型变量所属的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Fu
{
void fun1() {
System.out.println("F_1");
}
void fun2() {
System.out.println("F_2");
}
}
class Zi extends Fu
{
void fun1() {
System.out.println("Z_1");
}
void fun3() {
System.out.println("Z_3");
}
}

class Demo{
public static void main(String[] args){
Fu f = new Zi();
f.fun1(); //Z_1
f.fun2(); //F_2
//f.fun3(); 必须注销否则编译不通过
}
}

接口和抽象例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class MainBoard
{
public void runBoard() {
System.out.println("mainboard run");
}
public void PICopen(PIC p) {
if(p!=null) {
p.open();
p.close();
}
}
}
interface PIC
{
public void open();
public void close();
}
class NetCard implements PIC
{
public void open() {
System.out.println("NetCardopen");
}
public void close() {
System.out.println("NetCardclose");
}
}
class Demo{
public static void main(String[] args){
MainBoard b = new MainBoard();
b.runBoard();
b.PICopen(null); //空引用对象
b.PICopen(new NetCard()); //PIC p = new NetCard();
}
}

这个例子中,MainBoard类的成员函数调用了接口类PIC。当需要NetCard时,将PIC实现,并在MainBoard对象中调用runPIC函数即可。当需要SoundCard时,重复上述过程即可。扩展性极强。[]

Object的equals()的覆写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Example{
private int num;
Example(int num){
this.num = num;
}
public boolean equals(Object obj){
if(!(obj instanceof Example)) //判断是否为本类方法***
return false;
Example e = (Example)obj; //强制转换,由于对于变量而言 编译和运行都参考引用型变量所属的类,即Object类。
return this.num == e.num;
}
}
class Demo{
public static void main(String[] args){
Example e1 = new Example(4);
Example e2 = new Example(4);
System.out.println(e1.equals(e2));
}
}

*内部类(不常用)

1.内部类可以直接访问外部类中的成员,包括私有。因为内部类中持有一个外部类的引用,格式为 外部类名.this

2.外部类要访问内部类,必须建立内部类对象。

3.内部类可以被静态修饰。被static修饰后,只能访问外部类的静态成员。此时调用内部类中的非静态函数就可以通过new Outer.Inner().method() 。调用静态函数则可以通过Outer.Inner.method()。同时,非静态内部类不能定义静态成员。

4.外部类中的静态函数访问内部类时,内部类也需要是静态的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Outer
{
private int x = 3;
class Inner{//内部类可以被私有修饰
void function(){
System.out.println("inner :" + x);//相当于Outer.this.x
}
}
void method(){
Inner in = new Inner();
in.function();
}
}
class Demo
{
public static void main(String[] args){
//Outer out = new Outer();
//out.method();
Outer.Inner in = new Outer().new Inner();
//外部类名.内部类名 变量名 = 外部类对象.内部类对象;
in.function();
}
}

局部内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Outer
{
int x = 3;
void method(final int a){
final int y =4; //jdk8之前必须加final,之后默认加final
class Inner //该内部类在成员函数的内部
{
void function()
{
System.out.println(a);
}
}
new Inner().function();
}
}
class Demo
{
public static void main(String[] args){
Outer out = new Outer();
out.method(7);//7
}
}

内部类定义在局部时,不可以被成员修饰符修饰;可以直接访问外部类中的成员,但是不可以访问它所在的局部中的变量,除非被final修饰。

匿名内部类

匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class AbsDemo{
void show();
}
class Outer{
int x =3;
class Inner extends AbsDemo{
void show()
{
System.out.println("show:"+x);
}
}
public void function(){
new Inner().show();
}
}

其中,第6-11行是在定义该内部类,第13行是创建内部类对象。如果要将内部类变为匿名内部类,则考虑无法通过具体的类名称来使用内部类。因此匿名内部类的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class AbsDemo{
void show();
}
class Outer{
int x =3;

public void function(){
//new Inner().show();
new AbsDemo() //用父类替代子类名称
{
void show() //然后直接new过程中实例化方法
{
System.out.println("x="+x);
}
}.show();进行调用
}
}

23.40

0%