第五章 抽象类
5.1抽象类 abstract
- 包含抽象方法的类必须是抽象类
- 抽象方法 在父类中没有实现,但是在子类中必须实现 (要是在子类中也没有实现 子类也必须声明是抽象类)
- 通过abstract方法给子类提供设计规范 约束子类的设计
- 抽象类使用的要点(五点)
- 含有抽象方法的类只能定义成抽象类
- 抽象类不能实例化,即不能使用new关键字来实例(但是能够构造方法)
- 抽象类可以包含属性 方法 构造方法 但是构造方法不能用来new实例,只能用来被子类调用。
- 抽象类只能用来被继承
- 抽象方法必须被子类实现
abstract public class TestAbstract { abstract void shout(); //第一 没有实现 第二 子类中必须实现 } class Dog extends TestAbstract{ public void shout() { System.out.println("runrunrun"); } }
5.2 接口(是比抽象类还抽象的抽象类)
- 可以更加规范的对子类进行规范
- 接口中所有的方法都是抽象方法 不提供任何实现(但是在jdk 1.8之后不同了 可以有默认方法和静态方法实现 1.9之后有私有方法的实现)
- 接口的声明 接口只能定义不变的量 只能定义常量 (规范定义全用大写 单词之间使用下划线进行分隔)
- 要点 :类实现接口使用 implements关键字 接口继承接口使用extends关键字
public interface MyIntreface {//接口中所有的方法都是抽象方法 不提供任何实现 int MAX_AGE = 100;//同样 会加上public static final定义常量 void testo1() ;//对自动的加上public abstract } class Test01 implements MyIntreface{//使用implements 而不是extends @Override public void testo1() { // TODO Auto-generated method stub System.out.println("MyInterface01"); }
- 面向接口编程(以不变应万变)
- 抽象的东西是最稳定的
public class TestInterface { public static void main(String[] args) { Volant v = new Angle();//只能new angle v.fly(); GoodMan a = new GoodMan(); a.honest(); Angle b = new Angle(); b.fly(); b.honest(); } } //飞行接口 interface Volant{ int FLY_HEIGHT = 1000; void fly(); } //善良接口 interface Honest{ void honest(); } //定义一个天使类 class Angle implements Honest,Volant{ @Override public void fly() { // TODO Auto-generated method stub System.out.println("天使会飞"); } @Override public void honest() { // TODO Auto-generated method stub System.out.println("天使很善良"); } } //定义一个好人类 class GoodMan implements Honest{ @Override public void honest() { // TODO Auto-generated method stub System.out.println("好人很善良"); } } //定义一个鸟人类 class FlyMan implements Volant{ @Override public void fly() { // TODO Auto-generated method stub System.out.println("鸟人会飞"); } }
- 抽象的东西是最稳定的
5.3 内部类的分类
-
Java中的内部类主要是分为:四种 (非常的重要)
- 成员内部类
- 匿名内部类
- 局部内部类{很少使用}
- 静态内部类
-
成员内部类可以使用四种访问控制符修饰{private default protected public}
- 非静态内部类
- 类中定义的内部类特点:
- 内部类作为外部类的成员,可以直接访问外部类的成员(包括private成员),反之则不行。
- 内部类做为外部类成员,可声明为private、default(默认)、protected或public。
- 内部类成员只有在内部类的范围之内是有效的。
- 用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
- 编译后生成两个类: OuterClass.class 和OuterClass$InnerClass.class
- 成员内部类: 定义在类的内部,而且与成员方法、成员变量同级,即也是外围类的成员之一,因此 成员内部类 与 外围类 是紧密关联的 这种紧密关联指的是,成员内部类的对象的创建必须依赖于外围类的对象(即没有外围类对象,就不可能创建成员内部类)。因此,成员内部类有以下3个特点:
- 成员内部类可以访问外围类的所有成员,包括私有成员 静态变量;
- 成员内部类是不可以声明静态成员(包括静态变量、静态方法、静态成员类、嵌套接口),但有个例外—可以声明 static final的变量, 这是因为编译器对final类型的特殊处理,是直接将值写入字节码
- 成员内部类对象都隐式地保存了一个引用,指向创建它的外部类对象;或者说,成员内部类的入口是由外围类的对象保持着(静态内部类的入口,则直接由外围类保持着
- 成员内部类中的 this,new关键字:
- 获取外部类对象:
OuterClass.this
- 明确指定使用外部类的成员(当内部类与外部类的名字冲突时):
OuterClass.this.成员名
- 创建内部类对象的new:
外围类对象.new
- 获取外部类对象:
- 成员内部类可以继续包含成员内部类,而且不管一个内部类被嵌套了多少层,它都能透明地访问它的所有外部类所有成员 成员内部可以继续嵌套多层的成员内部类,但无法嵌套静态内部类;静态内部类则都可以继续嵌套这两种内部类
//测试非静态内部类 public class TestInerClass { public static void main(String[] args) { Outer.Inner inner = new Outer().new Inner();//创建内部类对象 inner.shou(); } } class Outer{ private int age = 10; public void testOuter() { System.out.println(""); } class Inner{//inner 是内部类 可以使用外部类的属性和方法 public void shou() { System.out.println(Outer.this.age); } } }
- 类中定义的内部类特点:
- 静态内部类 不需要使用外部类的对象进行依托 可以看做是一个静态成员 静态内部类: 一般也称”静态嵌套类“,在类中用static声明的内部类 因为是static,所以不依赖于外围类对象实例而独立存在,静态内部类的可以访问外围类中的所有静态成员,包括private的静态成员 但是不能访问非静态成员同时静态内部类可以说是所有内部类中独立性最高的内部类,其创建对象、继承(实现接口)、扩展子类等使用方式与外围类并没有多大的区别 静态内部类中也可以使用main方法 能创建静态方法 也能创建成员方法
public class TestStaticInnerClass { public static void main(String[] args) { Outer2.Inner2 inner =new Outer2. Inner2();//不用再用创建外部类的对象 } } class Outer2{ static class Inner2{ } }
- 非静态内部类
-
匿名内部类的使用 与局部内部类很相似,只不过匿名内部类是一个没有给定名字的内部类,在创建这个匿名内部类后,便会立即用来创建并返回此内部类的一个对象引用 匿名内部类没有构造器 构造代码块充当了构造器的作用
- 作用:匿名内部类用于隐式继承某个类(重写里面的方法或实现抽象方法)或者实现某个接口 适合只使用一次的类 比如键盘监听操作等
- 匿名内部类的访问限制: 与局部内部类一样 不能使用访问修饰符
- 匿名内部类的优缺点:
- 优点: 编码方便快捷;
- 缺点:
- 只能继承一个类或实现一个接口,不能再继承其他类或其他接口。
- 只能用于创建一次对象实例;
//测试匿名内部类 public class TestAnoymousInnerClass { public static void test01(A a) { a.aa(); } public static void main(String[] args) { TestAnoymousInnerClass.test01(new A() { @Override public void aa() { // TODO Auto-generated method stub System.out.println("aa"); } }); } } interface A{//接口a 这样的接口叫做函数式接口 能使用lambda表达式(以后再说) void aa(); }
-
局部内部类 是定义在方法中的(不常使用)、构造器、初始化块中声明的类,在结构上类似于一个局部变量。因此局部内部类是不能使用访问修饰符
-
局部内部类的三个需要注意的点:
- 对于局部变量,局部内部类只能访问final的局部变量 (和 变量的生命周期有关)
- 对于类的全局成员,局部内部类定义在实例环境中(构造器、对象成员方法、实例初始化块),则可以访问外围类的所有成员;但如果内部类定义在静态环境中(静态初始化块、静态方法),则只能访问外围类的静态成员
- 方法中的内部类 在方法的内部中进行良好的封装
public class OuterClass { private int a = 21; static {//静态域中的局部内部类 class LocalClass1{ // int z = a; //错误,在静态的作用域中无法访问对象成员 } } {//实例初始化块中的局部内部类 class localClass2{ } } public OuterClass(){ int x = 2; final int y = 3; // x = 3;//若放开此行注释,编译无法通过,因为局部变量x已经是final类型 //构造器中的局部内部类 class localClass3{ int z = y; //可以访问final的局部变量 int b = a;//可以访问类的所有成员 //访问没有用final修饰的局部变量 int c = x; } } public void createRunnable() { final int x = 4; //方法中的局部内部类 class LocalClass4 implements Runnable {// @Override public void run() { System.out.println("局部final变量:"+x); System.out.println("对象成员变量:"+a); } } } }
-
-
使用内部类的好处:虽然引入内部类让程序的结构变得复杂,但是内部类的好处就是,就是我们可以方便的操作外部类的私有访问
-
总结:
类 型 访问修饰符 声明静态成员 绑定外围类 静态内部类 四种访问修饰符 可以声明 不绑定 成员内部类 四种访问修饰符 除 final static 的变量外,其余静态成员都不行 绑定 局部内部类 不可以声明 不可以声明 取决于此内部类的声明环境 匿名内部类 不可以声明 不可以声明 取决于此内部类的声明环境
5.4 String类的基本用法
- String类又被称为不可变字符序列,位于java.lang包中,java程序默认导入java.lang包中的所有的类
字符串常量会方在字符串常量池中(使用的是final进行修饰的静态类)public class TestString { public static void main(String[] args) { String str = "123" ; String string = new String("def"); String s = "asdf" + "adfad";//可以进行字符串的拼接 String st = "18" + 18; System.out.println("########################"); String s1 = "wang"; String s2 = "wang"; String s3 = new String("wang"); System.out.println(s1 ==s2);//放在了字符串常量池中,所以是相同的 System.out.println(s2 ==s3);//创建了一个新的对象 但是不同 System.out.println(s2.equals(s3));//比较字符串一般使用equals方法 字符串一般比较内容 } }
- String 类的常用方法:(String类中的方法 都非常的重要且常用)
public class TestString2 { public static void main(String[] args) { String s1 = "core Java"; String s2 = "Core java"; System.out.println(s1.charAt(2));//取索引为2 的字符 System.out.println(s1.length());//字符串的长度 System.out.println(s1.equals(s2));//比较两个字符串的内容是否相等 System.out.println(s1.equalsIgnoreCase(s2));//比较两个字符串(忽视大小写) System.out.println(s1.indexOf("Java"));//字符串中是否包含“java”返回字符串的索引 没有就返回-1 String s = s1.replace(' ', '&');//将是s1 中的空格代替成& System.out.println("result is" + s1);//s1中的空格不会替换 因为字符串中是不可变类型 System.out.println("result is" + s);//s中的空格会被替换 System.out.println("###############################"); String a = ""; String a1 = "How are you?"; System.out.println(a1.startsWith("How"));//字符串是否以how开头 System.out.println(a1.endsWith("you"));//字符串是否以有结尾 a = a1.substring(4);//提取子字符串从下标4到末尾 System.out.println(a); a = a1.substring(4, 7);//提取子字符串[4,7)不包括7 System.out.println(a); a = a1.toLowerCase();//转小写 System.out.println(a); a = a1.toUpperCase();//转大写 System.out.println(a); String a2 = "How old are you !!"; a = a2.trim();//去除字符串首尾的空格 但是中间的不能去除 System.out.println(a); } }
5.5数组的拷贝(数组的删除、数组的扩容 数组的插入实际上都是数组的拷贝)
//测试数组的拷贝
public class TestArrayCopy {
public static void main(String[] args) {
String [] s1 = {"aa","bb","cc","dd","ee"};
String [] s2 = new String [10];
System.arraycopy(s1, 2, s2, 6, 3);//从s1中的第二个开始(数组的开始索引是0)
//向s2 中的第六个位置拷贝3个数据
for(int i = 0;i<s2.length;i++) {
System.out.println(i + "--" + s2[i]);
}
testBaiscCopy();
String[] str = {"123","1234","12345"};
testArrayCopy(str, 1);
}
//测试从数组中删除某个元素(本质上是数组的拷贝)
public static void testBaiscCopy() {
String [] s1 = {"aa","bb","cc","dd","ee"};
//String [] s2 = new String [10];
System.arraycopy(s1, 3, s1, 3-1, s1.length-3);
s1[s1.length-1] = null;
for(int i = 0;i<s1.length;i++) {
System.out.println(s1[i]);
}
}
public static String[] testArrayCopy(String [] s ,int index) {
System.arraycopy(s, index+1, s, index, s.length-index-1);
s[s.length-1] = null;
for(int i = 0;i<s.length;i++) {
System.out.println(s[i]);
}
return s;
}
extendArrayRange();
}
//数组的扩容本质上是将数组的内容放在一个新建的更大的数组上
public static void extendArrayRange() {
String [] s1 = {"aa","bb","cc"};
String [] s2 = new String [s1.length+10];
System.arraycopy(s1, 0, s2, 0, s1.length);
/*for(int i = 0;i<s2.length;i++) {
System.out.println(s2[i]);
}*/
for(String t :s2) {//使用增强for循环将s2 中的元素循环输出
System.out.println(t);
}
}
}
5.6 Arrays工具类 在java.util中
//测试Arrays类的使用
public class TestArraysClass {
public static void main(String[] args) {
int [] a = {10,20,30};
int [] c = {1,5,3,4,8,6,9,7,8,2,0};
for(int b :a) {
System.out.println(b);
}
System.out.println(a);
System.out.println(Arrays.toString(a));//Arrays中的toString与Object中的toString不一样只是方法的名字相同
System.out.println(a.toString());//getClass().getName() + "@" + Integer.toHexString(hashCode());
Arrays.sort(c);//排序
System.out.println(Arrays.toString(c));
System.out.println(Arrays.binarySearch(c, 5));//查找数组中是否有所查的元素找到了就返回索引没有找到就返回-1
}
}
5.7多维数组(数组中套数组)
- 使用数组存储表格数据(使用多维数组)不常用 以后都是使用面向对象的思想进行存储
public class Test2DimensionArrays { public static void main(String[] args) { int[] a = new int[3]; Car[] cars = new Car[3]; int[] [] c = new int [3][]; c[0] = new int[] {20,30}; c[1] = new int[] {115,115}; c[2] = new int[] {0,30}; System.out.println(c[1][1]);//二维数组将{20,30}的地址放在了数组c中的第0个位置上 //同样的可以纯静态初始化二维数组 int[][] b = { {20,30}, {20,30,40}, {20,30,40,50} }; System.out.println(b[2][3]); } } class Car{ }
5.8冒泡排序&折半查找
- 冒泡排序要点:
- 比较相邻的元素 如果第一个比第二个打就交换顺序
- 每一对都进行比较从开始到末尾的一队(那么最后的一位就是最大的)
- 在针对上述的元素进行排序(除了最后的一个元素)
- 直到没有元素进行比较
public class TestBubbleSort { public static void main(String[] args) { int[] values = {3,1,5,7,4,8,9,0,2,6}; int temp = 0; for(int i = 0;i<values.length-1;i++) { boolean flag = true;//标志位 for(int j = 0;j<values.length-1-i;j++) { //交换顺序 定义一个中间变量temp if(values[j]>values[j+1]) { temp = values[j]; values[j]= values[j+1]; values[j+1] = temp; flag = false; } System.out.println(Arrays.toString(values)); } //冒泡排序的优化 当没有交换顺序时就跳出循环 if(flag) { System.out.println("结束!"); break; } } } }
- 二分法检索 又叫折半检索
- 前提是已经排好序了 要是没有进行排序不能使用折半查询
- 然后将数据取中间值与索取的值进行比较 效率非常的高
public class TestBinarySearch { public static void main(String[] args) { int[] arr = {1,5,3,4,8,5,9,8,9,6,3}; //Arrays.sort(arr); System.out.println(Arrays.toString(arr)); System.out.println(myBinarySearch(arr, 6));//有值便返回值的索引 System.out.println(myBinarySearch(arr, 7));//没有值便返回-1 } //使用二分法查找数组中所需的值,有就返回值的索引 前提是将数组排序 public static int myBinarySearch(int[] arr,int value) { Arrays.sort(arr); int low = 0; int high = arr.length-1; while(low<=high) { int mid = (low + high)/2; if(value == arr[mid]) { return mid; } if(value>arr[mid]) { low = mid +1; } if(value<arr[mid]) { high = mid -1; } } return -1; } }