高级语法
第三章:面向对象和高级语法
实例化:
注意,声明和实例化是两个过程。声明的过程是不分配内存空间的,只有实例化才会真正分配空间
-
实例变量只有实例化之后才能使用,而类变量直接用类名就可以使用
成员变量在堆,局部变量在栈
匿名对象
new Person().shout();
使用场景:
- 对一个对象只需要一次方法调用
- 作为实参传递给方法
方法的重载
参数的个数或者参数的数据类型必须不同
可变个数的形参
- (String[] args)//定义字符串数组
- (String... args)//JAVA特有的点点点的方式,这种参数在使用时与数组的使用方式相同
两种方法的区别:
? 第一种如果没有参数,需要输入null或者一个空数组;而第二种直接不填就好(...代表可以传递0到多个参数)
方法的参数传递
JAVA只有一种参数传递方法:值传递。即将实际参数值复制到方法的形参中,而参数本身不受影响(当然这种方法比较慢,但是JAVA吗23333)
this关键字
使用this的几个优点:
JAVA Bean
继承(extends)和多态
JAVA只支持单继承
继承让类与类之间产生了关系,不要仅仅为了获取其他类中的某个功能而去继承-》继承是要有逻辑关系在其中的
方法的重写(@override)
- 重写只能重写方法体,名称、参数列表和返回值类型均不能改
- 重写方法不能使用比被重写方法更加严格的访问权限
- 子类方法抛出的异常不能大于父类被重写方法的异常
题外话:ctrl+/:eclipse快速注释代码
如果子类和父类在同一个包下,那么对于父类的成员修饰符除了private之外,剩下的情况子类都可以使用父类的成员变量;如果不在一个包下,只有protected和public的成员变量可以使用super关键字
super和this类似,但是是用于调用父类的成员变量和方法的。
使用super,子类可以调用父类之上的所有父类层级
this和super的区别:
类对象的实例化过程
子类构造方法先入栈,父类再入栈,所以父类先执行
instanceof操作符
instance:实例
Object类
也叫基类
形参定义为object类型,则可以接受任何类型的类(注意,必须是个类!)
equal:比较引用对象:是否引用了同一个对象(也就是一个new出来的对象)
Person e=new person(); Person p=new person();//此时e和p指向的是堆中的两个不同的对象,此时用equal方法检测是false e=p;//此时e被重定向到p,此时再检测就是true了
对象的类型转换
从子类到父类:
从父类到子类:
e可以接受父类为Person类的各种类的对象,然后检查其是否是Student类或Student类的子类,如果是,就执行Student类中独有的方法,否则执行其他方法。==操作符和equals方法
只有指向同一个对象,==才为true,而比较的不是成员变量的值是否相等equals和==功能相同,除了下面说的几个特殊情况:
String对象的创建
而“不重复”只全体现在常量池上,如果使用New的方式,堆中每次都会创建一个新的对象。最特别的是最后一种情况,常量池中添加的是拼接钱的串,而堆中创建的却是拼接后的串
-
自动拆箱、装箱:和基本数据类型写法可以保持一致(转换更简单,使用起来比基本数据类型更方便、功能更强大)
包装类的功能:
借助包装类的类方法,将字符串转换为对应类型的数据(注意,只是借助其实现转换,并没有直接使用包装类)
- 字符串转其他基本数据类型
int i=Integer.parseInt("123");
boolean b=Boolean.parseBoolean("false");
- 其他基本数据类型转字符串
String istr=String.valueOf(i);
String batr=String.valueOf(b);
->综上,包装类主要应用于字符串和其他基本数据类型的应用
重写toString()
toString方法是直接用System.out.printIn()打印时调用的方法。默认的toString()是类Object的方法,由于其是所有类的父类,所以其功能比较泛,该方法就是输出内存地址(大多数情况下这是没什么用的)。所以各个类可以重写该方法以打印有意义的内容。
static关键字
static用来设置类属性和类方法
类属性被所有对象所共用,可以用来计数什么的
单例设计模式--设计模式之一
什么是设计模式?
单例
只有一个实例(实例化对象)
在整个软件系统运行过程中,这个类只被实例化一次,以后不论在哪都只调用这一个实例
应用的场景:
- 该对象的创建要消耗大量的时间和资源
- 重复new对象没有必要
两种实现方式:
理解main方法
初始化代码块
类的成员初始化过程:声明成员变量默认值-》显示初始化,执行代码块-》执行构造方法
在实际应用中,静态代码块用的比较多,用于初始化静态类属性(尤其对于一些比较复杂的静态类属性,例如属性是一个类的情况下),非静态的作用不太大匿名内部类
对Person类的方法进行了重载,所以其其实是一个Person的子类,故称为匿名内部类
匿名内部类中成员变量的初始化(如果要和父类的初始化有所不同的话)无法通过构造方法实现(因为没有类名,无法创建构造方法),只能通过代码块的方式完成final关键字
概况一下,final修饰的内容初始化之后就不能再修改了,所以变量要均大写,和常量保持一致(final修饰的变量是常量,常量必须显式赋值)抽象类
final和abstract是冲突的
模板方法设计模式--设计模式之二
接口
interface定义,implements使用
如果父类新增抽象方法,子类就必须实现,否则就需要定义为抽象类。
解决方法就是不要在父类中新增抽象方法,而是新增一个接口,由子类去选择是否实现这一接口
父类需要稳定的抽象,不能总是在改
需要描述一个交叉的关系时:
- 不能使用类的继承解决这个问题:会污染类的继承,使继承关系没有逻辑性
- 实现接口就好(接口是一类方法(动作)的集合)
对抽象类和接口的总结:
- 抽象类是对于一类事物的高度抽象,其中既有属性也有方法
- 接口是对方法的抽象,也就是对一系列动作的抽象
- 当需要对一类事物抽象的时候,应该使用抽象类,好形成一个父类;当需要对一系列动作抽象时,就使用接口
匿名内部类在接口中的应用
看到了以下一段代码:
这里的Runnable是接口,我们知道接口是不能实例化对象的,那这里是什么情况呢?其实这不是声明了一个接口类型的对象,而是一种多态机制,专业名词叫“匿名内部类”,实际上是创建了一个遵守了该接口的普通类(Object)对象。
类似的使用还有下面,可以看到,类能声明的接口几乎都可以声明
interface Shape { void draw(); } public class Main { // interface type as instance variable private Shape myShape; // interface type as parameter type for a constructor public Main(Shape s) { this.myShape = s; } // interface type as return type of a method public Shape getShape() { return this.myShape; } // interface type as parameter type for a method public void setShape(Shape s) { this.myShape = s; } public void letItSwim() { // interface type as a local variable Shape locaShape = null; locaShape = this.myShape; // interface variable can invoke methods // declared in the interface and the Object class locaShape.draw(); } }
这其实是一种变相的“用接口作为类型”,也是面向接口的编程的思想之一。
工厂模式——设计模式之三
在真正的开发工作中,都是合作开发,每个开发人员写一部分,集合到一起成为一个项目问题:一个开发人员要改代码,例如改类名,会影响其他人的工作。因此降低了工作效率
解决方法:写一个产品接口类,一个工厂接口类,其他人用工厂类而不是直接用产品类。通过工厂将new对象隔离,通过产品的接口接受不同实际产品的实现类,实例的类名的改变不影响其他合作开发人员的编程。其他开发人员只需要关注工厂类的使用,而修改代码只限于产品类的名称和工厂类的代码,不会影响工厂类的使用。(这种设计可能是架构师的任务(?ω?))
无论是内部类还是其他,JAVA除了很早期的一些特征外,剩下的特征都是为了解决一些特点问题必不可少的,不是冗余的功能
内部类
注意外部类调用内部类时方法
内部类的声明和外部类互相不影响,可以重名
内部类的作用:
函数式编程思想概述
面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法--强调做什么,而不是以什么形式做
传递一段代码--这才是我们真正的目的。Lambda表达式是JDK1.8中最重要的新特性,它打开了函数式Java开发的大门
使用Lambda表达式实现多线程
new Thread(()->{System.out.println("新线程创建了")};).start();
匿名内部类的好处和弊端
好处:可以帮助我们省去实现类的定义
弊端:匿名内部类的语法太复杂了
Lambda表达式适用的情况
- 无参数
- 无返回值
- 代码块
在这样的情况下,用Lambda表达式更加简单
Lambda表达式的格式
Lambda表达式由参数、箭头、代码三部分组成
(参数类型 参数名称)->(代码语句)
实例
- 实例一:
题外话:用泛型构造sort函数中的Comparator
Arrays.sort(arr,(Person o1,Person o2)->{
return o1.getAge()-o2.getAge();
});
- 实例二
有参数有返回值的情况
Lambda表达式中,凡是可以根据上下文推导得知的信息,都可以省略
- 小括号内参数的类型可以省略
- 如果小括号只有一个参数,则小括号和类型可以省略
- 如果大括号只有一个语句,则无论是否有返回值,大括号、return和语句分句都可以省略(注意这里,要是省略的话,括号、return和分号必须一起省略
举个类似的例子:
上面那个例子中,参数类型、大括号、return和分号都省略了
lambda表达式的使用前提
这也可以理解,如果接口中的方法不止有一个,编译器在判断lambda表达式对应的到底是哪一个方法时就很困难
有且仅有一个抽象方法的接口,叫做函数式接口
关于Lambda表达式的详细内容,参见https://segmentfault.com/a/1190000009186509
![1568038836654](https://user-images.githubusercontent.com/36098426/67142195-025a4f80-f296-11e9-8401-904c4245f389.png)
>空括号用于表示一组空的参数。例如 `() -> 42`。
>
>当有且仅有一个参数时,如果不显式指明类型,则不必使用小括号。例如 `a -> return a*a`。
Lambda表达式的应用:
>线程可以初始化如下:
>
>
>
>```
>// Old way
>new Thread(new Runnable() {
> @Override
> public void run() {
> System.out.println("Hello world");
> }
>}).start();
>
>// New way
>new Thread(
> () -> System.out.println("Hello world")
>).start();
>```
>
>事件处理可以用 Java 8 使用 Lambda 表达式来完成。以下代码显示了将 `ActionListener` 添加到 UI 组件的新旧方式:
>
>```
>// Old way
>button.addActionListener(new ActionListener() {
> @Override
> public void actionPerformed(ActionEvent e) {
> System.out.println("Hello world");
> }
>});
>
>// New way
>button.addActionListener( (e) -> {
> System.out.println("Hello world");
>});
>```
>
>## 6.3 遍例输出(方法引用)
>
>输出给定数组的所有元素的简单代码。请注意,还有一种使用 Lambda 表达式的方式。
>
>```
>// old way
>List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
>for (Integer n : list) {
> System.out.println(n);
>}
>
>// 使用 -> 的 Lambda 表达式
>list.forEach(n -> System.out.println(n));
>
>// 使用 :: 的 Lambda 表达式
>list.forEach(System.out::println);
>```
>
>## 6.4 逻辑操作
>
>输出通过逻辑判断的数据。
>
>```
>package com.wuxianjiezh.demo.lambda;
>
>import java.util.Arrays;
>import java.util.List;
>import java.util.function.Predicate;
>
>public class Main {
>
> public static void main(String[] args) {
> List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
>
> System.out.print("输出所有数字:");
> evaluate(list, (n) -> true);
>
> System.out.print("不输出:");
> evaluate(list, (n) -> false);
>
> System.out.print("输出偶数:");
> evaluate(list, (n) -> n % 2 == 0);
>
> System.out.print("输出奇数:");
> evaluate(list, (n) -> n % 2 == 1);
>
> System.out.print("输出大于 5 的数字:");
> evaluate(list, (n) -> n > 5);
> }
>
> public static void evaluate(List list, Predicate predicate) {
> for (Integer n : list) {
> if (predicate.test(n)) {
> System.out.print(n + " ");
> }
> }
> System.out.println();
> }
>}
>```
>
>运行结果:
>
>```
>输出所有数字:1 2 3 4 5 6 7
>不输出:
>输出偶数:2 4 6
>输出奇数:1 3 5 7
>输出大于 5 的数字:6 7
>```
>
>## 6.4 Stream API 示例
>
>`java.util.stream.Stream`接口 和 Lambda 表达式一样,都是 Java 8 新引入的。所有 `Stream` 的操作必须以 Lambda 表达式为参数。`Stream` 接口中带有大量有用的方法,比如 `map()` 的作用就是将 input Stream 的每个元素,映射成output Stream 的另外一个元素。
>
>下面的例子,我们将 Lambda 表达式 `x -> x*x` 传递给 `map()` 方法,将其应用于流的所有元素。之后,我们使用 `forEach` 打印列表的所有元素。
>
>```
>// old way
>List list = Arrays.asList(1,2,3,4,5,6,7);
>for(Integer n : list) {
> int x = n * n;
> System.out.println(x);
>}
>
>// new way
>List list = Arrays.asList(1,2,3,4,5,6,7);
>list.stream().map((x) -> x*x).forEach(System.out::println);
>```
>
>下面的示例中,我们给定一个列表,然后求列表中每个元素的平方和。这个例子中,我们使用了 `reduce()` 方法,这个方法的主要作用是把 Stream 元素组合起来。
>
>
>
>```
>// old way
>List list = Arrays.asList(1,2,3,4,5,6,7);
>int sum = 0;
>for(Integer n : list) {
> int x = n * n;
> sum = sum + x;
>}
>System.out.println(sum);
>
>// new way
>List list = Arrays.asList(1,2,3,4,5,6,7);
>int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
>System.out.println(sum);
>```
Lambda表达式和匿名类之间的区别
>- `this` 关键字。对于匿名类 `this` 关键字解析为匿名类,而对于 Lambda 表达式,`this` 关键字解析为包含写入 Lambda 的类。
>- 编译方式。Java 编译器编译 Lambda 表达式时,会将其转换为类的私有方法,再进行动态绑定。
异常
异常的概念
编程时主要面对的就是运行时错误
常见异常:
捕获异常
try-catch模型:
在try中的代码,报错后就结束,不会执行try中剩下的部分
抛出异常
throw exception
main方法也可以抛出异常,但是抛出就直接甩到虚拟机了,程序中对其将无法处理,所以一般不这样做
重写方法不能抛出比被重写方法范围更大的异常类型,也就是说,子类不能抛出比父类更大范围的异常
人工抛出异常:
JAVA提供的异常类一般是够用的,只有特殊情况才需要自定义,情况少见
集合
集合概念
HashSet
不可重复,指的是hashcode是否相同,而不是equals是否相同
如果想让集合只能存相同类型的对象:使用泛型
泛型
Set set1= new HashSet ();
TreeSet
如图,如果要在TreeSet中放入自定义类型的对象,则必须实现compare方法
使用TreeSet必须放入相同类型对象
迭代器
Set set=new HashSet();
Iterator it= set.iterator();
//迭代输出法一
while(it.hasNext()){
Systems.out.printLn(it.next());//注意,调用next()会使迭代器加一
}
//迭代输出法二:foreach法(注意虽然思想是foreach,但是代码中还是只有for没有each)
for(Object obj:set){//将set中的每一个值取出来,赋值给obj
? Systems.out.printLn(obj);
}
List
add添加数据,addAll在指定的下标位置添加数据
Map
根据key可以修改value或者移出键
工具类Collections
泛型
泛型通配符
第三个比较有用:只有实现了Comparable接口才能传入,这样方法内部就可以使用Comparable的方法而保证所有传入的对象都可以操作了
枚举
使用“枚举类名.枚举”相对于调用了构造方法。但每次执行获得的都行相同对象:枚举类中的每个枚举都是单例的
函数式接口
Lambda不是匿名内部类的语法糖
注意,函数式接口只限制抽象(abstract)方法只能有一个,对于其他类型的方法,比如静态方法、私有方法等没有限制
@FunctionalInterface:检测接口是否为函数式接口,如果是则可以编译成功,否则就会编译失败
函数式接口可以作为方法的参数和返回值使用
- 参数使用函数式接口
Lambda表达式的延迟执行
定义函数式接口
package jd; /* *@author JiaDing */ @FunctionalInterface public interface MessageBuilder { public abstract String builderMessage(); }
调用函数式接口
package jd; /* * Lambda表达式的特点:延迟加载 * Lambda表达式的前提:存在函数式接口 */ public class Jd { // 定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口 public static void showLog(int level, MessageBuilder mb) { if (level == 1) { System.out.println(mb.builderMessage()); } } public static void main(String[] args) { String msg1 = "Hello"; String msg2 = "World"; String msg3 = "Java"; showLog(1, () -> { // 返回一个拼接好的字符串 return msg1 + msg2 + msg3; }); } }
这里和直接传递参数相比的区别在于,这里实际上是实现了函数式接口中的方法并在方法的return中返回拼接,如果条件不满足,那么这个方法就不会被调用,字符串自然也不会拼接了
实例1:
注解
@Deprecated过时方法不是不能调用,只是显示出来,便于选择和之后的迭代
示例:
javadoc test1.java//抽取注解到文档中
生成的文件们:
@Target(ElementType.FIELD)//声明这个注解类是给其他类的属性做注解
@Rectention(RetentionPolicy.RUNTIME)//定义注解的生命周期
@Documneted//表示将注解写到文档中
@interface TestAnn{
? public int id() default 0;//default是默认值
public String desc() default "";
}
使用:
示例,依然以之前使用配置文件来调用任意类的任意方法的例子为例: